├── .gitignore ├── LICENSE ├── Plugin.php ├── README.md ├── assets └── javascript │ └── websocket.js ├── classes ├── MessageComponent.php └── ServerFactory.php ├── components └── WebSocket.php ├── composer.json ├── console └── RunCommand.php └── updates └── version.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Leo Cavalcante 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 | -------------------------------------------------------------------------------- /Plugin.php: -------------------------------------------------------------------------------- 1 | registerConsoleCommand('websockets.run', 'LeoCavalcante\WebSockets\Console\RunCommand'); 14 | } 15 | 16 | public function pluginDetails() 17 | { 18 | return [ 19 | 'name' => 'WebSockets', 20 | 'description' => 'Provides some WebSockets features.', 21 | 'author' => 'LeoCavalcante', 22 | 'icon' => 'icon-leaf' 23 | ]; 24 | } 25 | 26 | public function registerComponents() 27 | { 28 | return [ 29 | 'LeoCavalcante\WebSockets\Components\WebSocket' => 'websocket', 30 | ]; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # October WebSockets 2 | 3 | Add real-time features to your OctoberCMS project. 4 | 5 | ## Usage 6 | 7 | ### Start the server 8 | 9 | ```shell 10 | php artisan websockets:run 11 | ``` 12 | 13 | You can specify a `--port` if you want to, default is `8080`. 14 | 15 | ### Attach the component 16 | 17 | Add the client component to your page/layout. You can set an `uri` property if you are running on a different port. Default is `ws://localhost:8080/`. 18 | 19 | ```ini 20 | [websocket] 21 | == 22 | ``` 23 | 24 | ### The API 25 | 26 | It uses an AJAX framework-like API, is familiar for OctoberCMS developers 27 | 28 | ```html 29 | data-websocket-event="name" 30 | ``` 31 | It fires up `send()` method with the specified event name. 32 | 33 | ```html 34 | data-websocket-oneventname="console.log(event)" 35 | ``` 36 | It evals the informed script, just like AJAX framework with a data argument. 37 | 38 | **You are ready to rock on sockets. Build a chat app!** 39 | 40 | Don't forget to add [jQuery](http://jquery.com/) and `{% scripts %}` placeholder. 41 | ```html 42 | url = "websockets" 43 | [websocket] 44 | == 45 | 46 | 47 | 48 | 49 | Web Sockets 50 | 51 | 52 | 53 | 54 |
55 | 56 | 57 |
58 | 59 | 60 | {% scripts %} 61 | 62 | 63 | ``` 64 | -------------------------------------------------------------------------------- /assets/javascript/websocket.js: -------------------------------------------------------------------------------- 1 | if (window.jQuery === undefined) 2 | throw new Error('The jQuery library is not loaded. The WebSockets plugin cannot be initialized.'); 3 | 4 | if (window.WebSocket === undefined) 5 | throw new Error('Your browser does not support WebSockets API. The WebSockets plugin cannot be initialized.'); 6 | 7 | +function ($) { "use strict"; 8 | var 9 | NAMESPACE = 'websocket', 10 | TRIGGER_ATTR = NAMESPACE + '-event', 11 | LISTENER_ATTR = NAMESPACE + '-on', 12 | TRIGGER_SELECTOR = '[data-' + TRIGGER_ATTR + ']'; 13 | 14 | var properties = queryStringToObject(getQueryString(), true), 15 | websocket = null; 16 | 17 | function init() { 18 | websocket = new WebSocket(properties.uri); 19 | websocket.onmessage = onMessage; 20 | } 21 | 22 | function onMessage(message) { 23 | var event = JSON.parse(message.data); 24 | 25 | if (!event.name) { 26 | throw new Error('Invalid event name.'); 27 | } 28 | 29 | var attrName = LISTENER_ATTR + event.name, 30 | selector = '[data-' + attrName + ']'; 31 | 32 | $(document).trigger(jQuery.Event(NAMESPACE + ':' + event.name, {payload: event.payload})); 33 | 34 | $(selector).each(function () { 35 | eval('(function(event) {' + $(this).data(attrName) + '}.call(this, event))'); 36 | }); 37 | } 38 | 39 | function websocketSend() { 40 | var $el = $(this), 41 | $form = $el.closest('form'), 42 | data = queryStringToObject($form.serialize()), 43 | eventName = $el.data(TRIGGER_ATTR); 44 | 45 | var event = { 46 | name: eventName, 47 | payload: data 48 | }; 49 | 50 | websocket.send(JSON.stringify(event)); 51 | } 52 | 53 | $.fn.websocketSend = websocketSend; 54 | 55 | $(document).on('submit', TRIGGER_SELECTOR, function (event) { 56 | $(this).websocketSend(); 57 | event.preventDefault(); 58 | }); 59 | 60 | function queryStringToObject(queryString, decode) { 61 | var query = queryString.split('&'), 62 | obj = {}; 63 | 64 | for (var i = 0, l = query.length; i < l; i++) { 65 | var keyVal = query[i].split('='); 66 | obj[keyVal[0]] = decode ? decodeURIComponent(keyVal[1]) : keyVal[1]; 67 | } 68 | 69 | return obj; 70 | } 71 | 72 | function getQueryString() { 73 | var scriptTags = document.getElementsByTagName('script'); 74 | return scriptTags[scriptTags.length - 1].src.split('?')[1]; 75 | } 76 | 77 | init(); 78 | }(jQuery); 79 | -------------------------------------------------------------------------------- /classes/MessageComponent.php: -------------------------------------------------------------------------------- 1 | peers = new \SplObjectStorage; 13 | } 14 | 15 | public function onOpen(ConnectionInterface $conn) 16 | { 17 | $this->peers->attach($conn); 18 | } 19 | 20 | public function onMessage(ConnectionInterface $from, $message) 21 | { 22 | $event = json_decode($message); 23 | 24 | foreach ($this->peers as $peer) { 25 | if ($peer == $from && !empty($event->broadcast)) { 26 | continue; 27 | } 28 | 29 | $peer->send($message); 30 | } 31 | } 32 | 33 | public function onClose(ConnectionInterface $conn) 34 | { 35 | $this->peers->detach($conn); 36 | } 37 | 38 | public function onError(ConnectionInterface $conn, \Exception $e) 39 | { 40 | $conn->close(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /classes/ServerFactory.php: -------------------------------------------------------------------------------- 1 | 'WebSocket Component', 12 | 'description' => 'WebSocket client.' 13 | ]; 14 | } 15 | 16 | public function defineProperties() 17 | { 18 | return [ 19 | 'uri' => [ 20 | 'title' => 'URI', 21 | 'description' => 'WebSocket server URI', 22 | 'default' => 'ws://localhost:8080/', 23 | 'type' => 'string', 24 | 'validationPattern' => '^ws\:\/\/.*', 25 | 'validationMessage' => 'Enter a valid WebSocket URI' 26 | ] 27 | ]; 28 | } 29 | 30 | public function onRun() 31 | { 32 | $props = $this->getProperties(); 33 | $this->addJs('/plugins/leocavalcante/websockets/assets/javascript/websocket.js?'.http_build_query($props)); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "leocavalcante/oc-websockets", 3 | "description": "Add real-time features to your OctoberCMS app", 4 | "type": "library", 5 | "require": { 6 | "cboden/ratchet": "^0.3.5" 7 | }, 8 | "license": "MIT", 9 | "authors": [ 10 | { 11 | "name": "leocavalcante", 12 | "email": "lc@leocavalcante.com" 13 | } 14 | ], 15 | "minimum-stability": "dev" 16 | } 17 | -------------------------------------------------------------------------------- /console/RunCommand.php: -------------------------------------------------------------------------------- 1 | option('port'); 27 | $this->info("Server listening on $port"); 28 | ServerFactory::create($port)->run(); 29 | } 30 | 31 | /** 32 | * Get the console command arguments. 33 | * @return array 34 | */ 35 | protected function getArguments() 36 | { 37 | return []; 38 | } 39 | 40 | /** 41 | * Get the console command options. 42 | * @return array 43 | */ 44 | protected function getOptions() 45 | { 46 | return [ 47 | ['port', null, InputOption::VALUE_OPTIONAL, 'WS server port.', 8080], 48 | ]; 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /updates/version.yaml: -------------------------------------------------------------------------------- 1 | 0.0.1: Work in Progress 2 | 0.1.0: API changes 3 | 0.2.0: Updated how dependencies are required 4 | 0.2.1: Update to new handle API method 5 | --------------------------------------------------------------------------------