├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── example ├── client.js └── server.js ├── index.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log* 3 | package-lock.json -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 'stable' 4 | - '5' 5 | - '4' -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Yerko Palma 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # choo-websocket [![stability][0]][1] 2 | [![npm version][2]][3] [![build status][4]][5] 3 | [![downloads][8]][9] [![js-standard-style][10]][11] 4 | 5 | Small wrapper around [WebSocket][WebSocket] browser API, for choo apps 6 | 7 | ## Usage 8 | ```js 9 | var choo = require('choo') 10 | var html = require('choo/html') 11 | 12 | var app = choo() 13 | app.use(require('choo-websocket')()) 14 | app.route('/', mainView) 15 | app.mount('body') 16 | 17 | function mainView (state, emit) { 18 | return html` 19 |
20 | 21 |Send something through sockets:
22 | 23 | 24 | 25 | ` 26 | 27 | function onclick () { 28 | emit('ws:send', document.getElementById('message').value) 29 | document.getElementById('message').value = '' 30 | } 31 | } 32 | 33 | function live (state, emitter) { 34 | emitter.on('DOMContentLoaded', function () { 35 | emitter.on('ws:open', () => { 36 | console.log('Connection established') 37 | }) 38 | emitter.on('ws:message', (data, event) => { 39 | var msgElement = document.getElementById('results') 40 | msgElement.textContent = msgElement.textContent + data + '\n' 41 | }) 42 | }) 43 | } 44 | ``` 45 | 46 | ## Events 47 | ### `ws:error` | `ws.events.ERROR` 48 | Emitted if the WebSocket constructor or any of its methods throws an exception. 49 | 50 | ### `ws:open` | `ws.events.OPEN` 51 | Emitted when the connection is established and the `readyState` property of the 52 | `socket` changes to `OPEN`. You should only _listen_ to this method, since the 53 | constructor and the `add-socket` event silently open sockets for you. This event 54 | handler will get two arguments, the `event` object, and the `id` of the socket 55 | that got opened, you can use that id to directly access to the socket through 56 | `state.sockets[id]` or passing it to events like `send` and `close`. If you don't 57 | pass the id, it will asume the default socket (the one created when the plugin got 58 | registered). 59 | 60 | ### `ws:close` | `ws.events.CLOSE` 61 | Emitted when the connection is closed and the `readyState` property of the 62 | `socket` changes to `CLOSED`. When you listen to this, handler will take two 63 | arguments, the `event` object and the socket `id`. When you emit this, it will 64 | take three arguments the `code` for the close, the `reason` string and the `id` 65 | of the socket you want to close. If you don't pass the id, it will asume the 66 | default socket (the one created when the plugin got registered). 67 | 68 | ### `ws:send` | `ws.events.SEND` 69 | Emitted to send a message through the socket. Emit this event passing the data 70 | you want to send, and an optional `id` as reference of the socket that you want 71 | to send the message. 72 | 73 | ### `ws:message` | `ws.events.MESSAGE` 74 | Listen to this event to get messages from the socket. The handler of this event 75 | will get three arguments, the `data` sent, the whole `evet` and the `id` of the 76 | socket that got the message. 77 | 78 | ### `ws:add-socket` | `ws.events.ADD_SOCKET` 79 | Add a socket. Accept a `route`, an `options` object and an optional `id`, if you pass 80 | a route of an existing socket it will override it. If you don't pass any id, 81 | it will create a random string as id. This id is used to identify the WebSocket 82 | instance in the app state and to emit and listen for events only to specific 83 | instances. Keep in mind that this is agnostic to the server side implementation, so 84 | its up to you to handle different websockets from server. 85 | In the example folder, there is a simple implementation of _rooms_ using the [ws][ws] 86 | module. Some other libraries like [socket.io][socket.io] has this concept built in. 87 | 88 | ## API 89 | ### `plugin = ws([route], [opts])` 90 | 91 | The plugin accepts three optional parameters. You can pass the `route` for the 92 | web socket, which defaults to `window.location.host`. Notice that you don't need 93 | to specify the `ws` protocol. Also you can pass some options as a second argument. 94 | 95 | - `secure`: Boolean. Set to true if you are in a secure environment. If you mix 96 | environment, it will throw on creation of the socket. 97 | - `protocols`: Array or String. Use it to specify sub protocols for the socket. 98 | 99 | If the object is correctly created, then you have a sockets object where every 100 | propertie is a key to the WebSocket instance that you added. 101 | 102 | ## See Also 103 | 104 | - [choo-sse][choo-sse] - Small wrapper around server-sent event browser API, for choo apps. 105 | 106 | ## License 107 | [MIT](/LICENSE) 108 | 109 | [0]: https://img.shields.io/badge/stability-experimental-orange.svg?style=flat-square 110 | [1]: https://nodejs.org/api/documentation.html#documentation_stability_index 111 | [2]: https://img.shields.io/npm/v/choo-websocket.svg?style=flat-square 112 | [3]: https://npmjs.org/package/choo-websocket 113 | [4]: https://img.shields.io/travis/YerkoPalma/choo-websocket/master.svg?style=flat-square 114 | [5]: https://travis-ci.org/YerkoPalma/choo-websocket 115 | [6]: https://img.shields.io/codecov/c/github/YerkoPalma/choo-websocket/master.svg?style=flat-square 116 | [7]: https://codecov.io/github/YerkoPalma/choo-websocket 117 | [8]: http://img.shields.io/npm/dm/choo-websocket.svg?style=flat-square 118 | [9]: https://npmjs.org/package/choo-websocket 119 | [10]: https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square 120 | [11]: https://github.com/feross/standard 121 | [WebSocket]: https://developer.mozilla.org/en-US/docs/Web/API/WebSocket 122 | [choo-sse]: https://github.com/YerkoPalma/choo-sse 123 | [ws]: https://github.com/websockets/ws 124 | [socket.io]: https://github.com/socketio/socket.io/ 125 | -------------------------------------------------------------------------------- /example/client.js: -------------------------------------------------------------------------------- 1 | var choo = require('choo') 2 | var html = require('choo/html') 3 | 4 | var app = choo() 5 | app.use(require('..')({ secure: true })) 6 | app.use(require('choo-devtools')()) 7 | app.use(live) 8 | 9 | app.route('/', makeView('default')) 10 | app.route('/private', makeView('private')) 11 | app.mount('body') 12 | 13 | function makeView (id) { 14 | return function (state, emit) { 15 | return html` 16 | 17 | 18 |Send something through sockets:
19 | 20 | 21 | go private 22 | 23 | ` 24 | 25 | function onclick () { 26 | emit('ws:send', document.getElementById('message').value, id) 27 | console.log('sending to ' + id) 28 | document.getElementById('message').value = '' 29 | } 30 | } 31 | } 32 | 33 | function live (state, emitter) { 34 | emitter.on('DOMContentLoaded', function () { 35 | emitter.emit('ws:add-socket', window.location.host + '/private', { secure: true }, 'private') 36 | emitter.on('ws:open', (e, id) => { 37 | console.log('Connection stablished to socket ' + id) 38 | }) 39 | emitter.on('ws:message', (data, event, id) => { 40 | var msgElement = document.getElementById('results-' + id) 41 | msgElement.textContent = msgElement.textContent + data + '\n' 42 | }) 43 | }) 44 | 45 | emitter.on('ws:error', (err, id) => { 46 | console.error('Error on socket ' + id) 47 | console.error(err) 48 | }) 49 | } 50 | -------------------------------------------------------------------------------- /example/server.js: -------------------------------------------------------------------------------- 1 | const WebSocket = require('ws') 2 | const fastify = require('fastify')() 3 | 4 | fastify.decorate('io', new WebSocket.Server({ server: fastify.server })) 5 | fastify.register(require('fastify-bankai'), { 6 | entry: './example/client.js' 7 | }) 8 | 9 | fastify.io.on('connection', (socket, req) => { 10 | socket.url = req.url 11 | socket.on('disconnect', () => { 12 | console.log('someone left') 13 | }) 14 | // On message broadcast to everyone 15 | socket.on('message', data => { 16 | // Broadcast to everyone else 17 | fastify.io.clients.forEach(client => { 18 | console.log(socket.url, client.url) 19 | if (socket.url === client.url && client.readyState === WebSocket.OPEN) { 20 | client.send(data) 21 | } 22 | }) 23 | }) 24 | }) 25 | 26 | fastify.listen(process.env.PORT || 8080, process.env.IP || 'localhost', err => { 27 | if (err) throw err 28 | }) 29 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* global WebSocket */ 2 | var assert = require('assert') 3 | 4 | module.exports = websocket 5 | 6 | var events = websocket.events = { 7 | OPEN: 'ws:open', 8 | CLOSE: 'ws:close', 9 | SEND: 'ws:send', 10 | MESSAGE: 'ws:message', 11 | ADD_SOCKET: 'ws:add-socket', 12 | ERROR: 'ws:error' 13 | } 14 | 15 | function websocket (route, opts) { 16 | if (typeof route === 'object') { 17 | opts = route 18 | route = window.location.host 19 | } 20 | route = route || window.location.host 21 | opts = opts || {} 22 | assert.equal(typeof route, 'string', 'choo-websocket: route should be type string') 23 | assert.equal(typeof opts, 'object', 'choo-websocket: opts should be type object') 24 | 25 | return function (state, emitter) { 26 | assert.equal(typeof state, 'object', 'choo-websocket: state should be type object') 27 | assert.equal(typeof emitter, 'object', 'choo-websocket: emitter should be type object') 28 | 29 | state.sockets = {} 30 | createWebSocket(state, emitter, route, opts, 'default') 31 | emitter.on(events.CLOSE, function (code, reason, id) { 32 | // default close code is 1000 33 | // https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent#Status_codes 34 | code = code || 1000 35 | state.sockets[id || 'default'].close(code, reason) 36 | }) 37 | emitter.on(events.SEND, function (data, id) { 38 | console.log(data) 39 | state.sockets[id || 'default'].send(data) 40 | }) 41 | emitter.on(events.ADD_SOCKET, function (route, opts, id) { 42 | createWebSocket(state, emitter, route, opts, id) 43 | }) 44 | } 45 | } 46 | 47 | function createWebSocket (state, emitter, route, opts, id) { 48 | var socket = null 49 | id = id || (new Date() % 9e6).toString(36) 50 | try { 51 | socket = new WebSocket(`${opts.secure ? 'wss' : 'ws'}://${route}`, opts.protocols ? opts.protocols : undefined) 52 | socket.addEventListener('open', function (event) { 53 | state.sockets[id] = socket 54 | emitter.emit(events.OPEN, event, id) 55 | }) 56 | socket.addEventListener('close', function (event) { 57 | emitter.emit(events.CLOSE, event, id) 58 | }) 59 | socket.addEventListener('message', function (event) { 60 | emitter.emit(events.MESSAGE, event.data, event, id) 61 | }) 62 | return id 63 | } catch (e) { 64 | emitter.emit(events.ERROR, e) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "choo-websocket", 3 | "version": "2.0.0", 4 | "description": "Small wraper around WebSocket browser API, for choo apps", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "standard --verbose | snazzy", 8 | "start": "node example/server.js" 9 | }, 10 | "repository": "YerkoPalma/choo-websocket", 11 | "keywords": [ 12 | "websocket", 13 | "web", 14 | "socket", 15 | "choo" 16 | ], 17 | "author": "Yerko Palma", 18 | "license": "MIT", 19 | "devDependencies": { 20 | "choo": "^6.6.1", 21 | "choo-devtools": "^2.3.2", 22 | "fastify": "^0.37.0", 23 | "fastify-bankai": "^0.3.1", 24 | "snazzy": "^7.0.0", 25 | "standard": "^10.0.3", 26 | "ws": "^3.2.0" 27 | } 28 | } 29 | --------------------------------------------------------------------------------