├── .editorconfig ├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── .gitignore ├── license ├── package.json ├── readme.md ├── sockette.d.ts ├── sockette.jpg ├── src └── index.js └── test └── index.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_size = 2 6 | indent_style = tab 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.{json,yml,md}] 13 | indent_style = space 14 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: lukeed -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | name: Node.js v${{ matrix.nodejs }} 8 | runs-on: ubuntu-latest 9 | strategy: 10 | matrix: 11 | nodejs: [10, 12, 14, 20] 12 | steps: 13 | - uses: actions/checkout@v3 14 | - uses: actions/setup-node@v3 15 | with: 16 | node-version: ${{ matrix.nodejs }} 17 | 18 | - name: Install 19 | run: npm install 20 | 21 | - name: Test 22 | if: matrix.nodejs <= 20 23 | run: npm test 24 | 25 | - name: Coverage 26 | if: matrix.nodejs > 20 27 | run: | 28 | npm install -g c8 29 | c8 npm test 30 | c8 report --reporter=text-lcov > coverage.lcov 31 | bash <(curl -s https://codecov.io/bash) 32 | env: 33 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | *.lock 4 | *.log 5 | dist 6 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Luke Edwards (lukeed.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sockette", 3 | "version": "2.0.6", 4 | "repository": "lukeed/sockette", 5 | "description": "🧦 The cutest little WebSocket wrapper!", 6 | "module": "dist/sockette.mjs", 7 | "unpkg": "dist/sockette.min.js", 8 | "main": "dist/sockette.js", 9 | "types": "sockette.d.ts", 10 | "license": "MIT", 11 | "author": { 12 | "name": "Luke Edwards", 13 | "email": "luke.edwards05@gmail.com", 14 | "url": "lukeed.com" 15 | }, 16 | "scripts": { 17 | "build": "bundt", 18 | "pretest": "npm run build", 19 | "test": "tape test/*.js" 20 | }, 21 | "files": [ 22 | "dist", 23 | "sockette.d.ts" 24 | ], 25 | "keywords": [ 26 | "socket", 27 | "reconnect", 28 | "websocket" 29 | ], 30 | "devDependencies": { 31 | "bundt": "^0.4.0", 32 | "tape": "^4.10.1" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 |
2 | Sockette 3 |
4 | 5 |

Sockette

6 | 7 |
8 | 9 | version 10 | 11 | 12 | CI 13 | 14 | 15 | licenses 16 | 17 | 18 | downloads 19 | 20 |
21 | 22 |
The cutest little WebSocket wrapper! 🧦
23 | 24 |
25 | 26 | Sockette is a tiny (367 bytes) wrapper around `WebSocket` that will automatically reconnect if the connection is lost! 27 | 28 | In addition to attaching [additional API methods](#api), Sockette allows you to **reuse** instances, avoiding the need to redeclare all event listeners. 29 | 30 | You have direct access to the (current) underlying `WebSocket` within every `EventListener` callback (via `event.target`). 31 | 32 | 33 | ## Install 34 | 35 | ``` 36 | $ npm install --save sockette 37 | ``` 38 | 39 | 40 | ## Usage 41 | 42 | Unlike `WebSocket`, you should declare all event listeners on initialization: 43 | ```js 44 | const Sockette = require('sockette'); 45 | 46 | const ws = new Sockette('ws://localhost:3000', { 47 | timeout: 5e3, 48 | maxAttempts: 10, 49 | onopen: e => console.log('Connected!', e), 50 | onmessage: e => console.log('Received:', e), 51 | onreconnect: e => console.log('Reconnecting...', e), 52 | onmaximum: e => console.log('Stop Attempting!', e), 53 | onclose: e => console.log('Closed!', e), 54 | onerror: e => console.log('Error:', e) 55 | }); 56 | 57 | ws.send('Hello, world!'); 58 | ws.json({type: 'ping'}); 59 | ws.close(); // graceful shutdown 60 | 61 | // Reconnect 10s later 62 | setTimeout(ws.reconnect, 10e3); 63 | ``` 64 | 65 | 66 | ## API 67 | 68 | ### Sockette(url, options) 69 | 70 | Returns: `Sockette` 71 | 72 | Returns the `Sockette` instance. 73 | 74 | #### url 75 | Type: `String` 76 | 77 | The URL you want to connect to — Should be prefixed with `ws://` or `wss://`. This is passed directly to `WebSocket`. 78 | 79 | #### options.protocols 80 | Type: `String|Array` 81 | 82 | Either a single protocol string or an array of strings used to indicate sub-protocols. See the [`WebSocket` docs][MDN] for more info. 83 | 84 | #### options.timeout 85 | Type: `Number`
86 | Default: `1000` 87 | 88 | The amount of time (in `ms`) to wait in between reconnection attempts. Defaults to 1 second. 89 | 90 | #### options.maxAttempts 91 | Type: `Number`
92 | Default: `Infinity` 93 | 94 | The maximum number of attempts to reconnect. 95 | 96 | > **Important:** Pass `-1` if you want to disable this feature. Although, this is main reason to use Sockette! :joy: 97 | 98 | #### options.onopen 99 | Type: `Function` 100 | 101 | The `EventListener` to run in response to `'open'` events. It receives the `Event` object as its only parameter. 102 | 103 | > This is called when the connection has been established and is ready to send and receive data. 104 | 105 | > **Important:** Sockette will forget the number of previous reconnection attempts, so that the next time connection is lost, you will consistently retry `n` number of times, as determined by `options.maxAttempts`. 106 | 107 | #### options.onmessage 108 | Type: `Function` 109 | 110 | The `EventListener` to run in response to `'message'` events. It receives the `Event` object as its only parameter. 111 | 112 | > This is called when a message has been received from the server. You'll probably want `event.data`! 113 | 114 | #### options.onreconnect 115 | Type: `Function` 116 | 117 | The callback to run when attempting to reconnect to the server. 118 | 119 | If Sockette is automatically reconnecting in response to an `error` or unexpected `close` event, then your `onreconnect` callback will receive the forwarded `Event` object. 120 | 121 | #### options.onmaximum 122 | Type: `Function` 123 | 124 | The callback to run when the [`maxAttempts`](o#ptionsmaxattempts) limit has been met. 125 | 126 | This callback will receive the forwarded `Event` object from `onclose`. 127 | 128 | #### options.onclose 129 | Type: `Function` 130 | 131 | The `EventListener` to run in response to `'close'` events. It receives the `Event` object as its only parameter. 132 | 133 | > This is called when the connection has been closed for any reason. 134 | 135 | > **Important:** If the `event.code` is _not_ `1000`, `1001`, or `1005` an automatic reconnect attempt will be queued. 136 | 137 | #### options.onerror 138 | Type: `Function` 139 | 140 | The `EventListener` to run in response to `'error'` events. It receives the `Event` object as its only parameter. 141 | 142 | > This is called anytime an error occurs. 143 | 144 | > **Important:** If the `event.code` is `ECONNREFUSED`, an automatic reconnect attempt will be queued. 145 | 146 | ### send(data) 147 | 148 | Identical to [`WebSocket.send()`][send], capable of sending multiple data types. 149 | 150 | ### close(code, reason) 151 | 152 | Identical to [`WebSocket.close()`][close]. 153 | 154 | > **Note:** The `code` will default to `1000` unless specified. 155 | 156 | ### json(obj) 157 | 158 | Convenience method that passes your `obj` (Object) through `JSON.stringify` before passing it to [`WebSocket.send()`][send]. 159 | 160 | ### reconnect() 161 | 162 | If [`options.maxAttempts`](#optionsmaxattempts) has not been exceeded, enqueues a reconnection attempt. Otherwise, it runs your [`options.onmaximum`](#optionsonmaximum) callback. 163 | 164 | ### open() 165 | 166 | Initializes a new `WebSocket` — used on initialization and by [`reconnect()`](#reconnect). 167 | 168 | 169 | ## License 170 | 171 | MIT © [Luke Edwards](https://lukeed.com) 172 | 173 | [MDN]: https://developer.mozilla.org/en-US/docs/Web/API/WebSocket 174 | [close]: https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/close 175 | [send]: https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/send 176 | -------------------------------------------------------------------------------- /sockette.d.ts: -------------------------------------------------------------------------------- 1 | declare module "sockette" { 2 | export default class Sockette { 3 | constructor(url: string, options?: SocketteOptions); 4 | send(data: any): void; 5 | json(data: any): void; 6 | close(code?: number, reason?: string): void; 7 | reconnect(): void; 8 | open(): void; 9 | } 10 | 11 | export interface SocketteOptions { 12 | protocols?: string | string[]; 13 | timeout?: number; 14 | maxAttempts?: number; 15 | onopen?: (this: Sockette, ev: Event) => any; 16 | onmessage?: (this: Sockette, ev: MessageEvent) => any; 17 | onreconnect?: (this: Sockette, ev: Event | CloseEvent) => any; 18 | onmaximum?: (this: Sockette, ev: CloseEvent) => any; 19 | onclose?: (this: Sockette, ev: CloseEvent) => any; 20 | onerror?: (this: Sockette, ev: Event) => any; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /sockette.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukeed/sockette/75d9e73e511789ef77b7c6011800fcb0f602b24d/sockette.jpg -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | function noop() {} 2 | 3 | export default function (url, opts) { 4 | opts = opts || {}; 5 | 6 | var ws, num=0, timer=1, $={}; 7 | var max = opts.maxAttempts || Infinity; 8 | 9 | $.open = function () { 10 | ws = new WebSocket(url, opts.protocols || []); 11 | 12 | ws.onmessage = opts.onmessage || noop; 13 | 14 | ws.onopen = function (e) { 15 | (opts.onopen || noop)(e); 16 | num = 0; 17 | }; 18 | 19 | ws.onclose = function (e) { 20 | e.code === 1e3 || e.code === 1001 || e.code === 1005 || $.reconnect(e); 21 | (opts.onclose || noop)(e); 22 | }; 23 | 24 | ws.onerror = function (e) { 25 | (e && e.code==='ECONNREFUSED') ? $.reconnect(e) : (opts.onerror || noop)(e); 26 | }; 27 | }; 28 | 29 | $.reconnect = function (e) { 30 | if (timer && num++ < max) { 31 | timer = setTimeout(function () { 32 | (opts.onreconnect || noop)(e); 33 | $.open(); 34 | }, opts.timeout || 1e3); 35 | } else { 36 | (opts.onmaximum || noop)(e); 37 | } 38 | }; 39 | 40 | $.json = function (x) { 41 | ws.send(JSON.stringify(x)); 42 | }; 43 | 44 | $.send = function (x) { 45 | ws.send(x); 46 | }; 47 | 48 | $.close = function (x, y) { 49 | timer = clearTimeout(timer); 50 | ws.close(x || 1e3, y); 51 | }; 52 | 53 | $.open(); // init 54 | 55 | return $; 56 | } 57 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | const test = require('tape'); 2 | const fn = require('..'); 3 | 4 | test('exports', t => { 5 | t.is(typeof fn, 'function', 'exports a function'); 6 | t.end(); 7 | }); 8 | --------------------------------------------------------------------------------