# websocket-as-promised

websocket-as-promised logo

[![Actions Status](https://github.com/vitalets/websocket-as-promised/workflows/autotests/badge.svg)](https://github.com/vitalets/websocket-as-promised/actions)
[![Known Vulnerabilities](https://snyk.io/test/github/vitalets/websocket-as-promised/badge.svg?targetFile=package.json)](https://snyk.io/test/github/vitalets/websocket-as-promised?targetFile=package.json)
[![npm](https://img.shields.io/npm/v/websocket-as-promised.svg)](https://www.npmjs.com/package/websocket-as-promised)
[![license](https://img.shields.io/npm/l/websocket-as-promised.svg)](https://www.npmjs.com/package/websocket-as-promised)

A [WebSocket] client library with [Promise]-based API for browser and Node.js.

## Example
```js
import WebSocketAsPromised from 'websocket-as-promised';

// create instance of WebSocket connection
const wsp = new WebSocketAsPromised('ws://example.com');

// wait for WebSocket connection to open
await wsp.open();

// send some data
wsp.send('data');

// wait for connection to close
await wsp.close();
```

## Contents
* [Requirements](#requirements)
* [Installation](#installation)
* [Usage](#usage-in-browser)
  * [Browser](#usage-in-browser)
  * [Node.js](#usage-in-nodejs)
* [Sending data](#sending-raw-data)
  * [raw](#sending-raw-data)
  * [JSON](#sending-json)
  * [binary](#sending-binary)
  * [request / response](#sending-requests)
* [API](#api)
* [Changelog](#changelog)
* [License](#license)

## Requirements
* global `Promise` constructor

## Installation
```bash
npm i websocket-as-promised --save
```

## Usage in browser
```js
import WebSocketAsPromised from 'websocket-as-promised';

const wsp = new WebSocketAsPromised('ws://example.com');

wsp.open()
  .then(() => wsp.send('message'))
  .then(() => wsp.close())
  .catch(e => console.error(e));
```
Or with ES7 [async / await](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function):
```js
import WebSocketAsPromised from 'websocket-as-promised';

const wsp = new WebSocketAsPromised('ws://example.com');

(async () => {
  try {
    await wsp.open();
    wsp.send('message');
  } catch(e) {
    console.error(e);
  } finally {
    await wsp.close();
  }
})();
```

## Usage in Node.js
As there is no built-in WebSocket client in Node.js, you should use a third-party WebSocket npm package.

### Usage with [websocket](https://www.npmjs.com/package/websocket)
Here you should use W3C compatible client - [W3CWebSocket](https://github.com/theturtle32/WebSocket-Node/blob/master/docs/W3CWebSocket.md):
```js
const WebSocketAsPromised = require('websocket-as-promised');
const W3CWebSocket = require('websocket').w3cwebsocket;

const wsp = new WebSocketAsPromised('ws://example.com', {
  createWebSocket: url => new W3CWebSocket(url)
});

wsp.open()
  .then(() => wsp.send('message'))
  .then(() => wsp.close())
  .catch(e => console.error(e));
```

### Usage with [ws](https://www.npmjs.com/package/ws)
Here it is important to define `extractMessageData` option as event data are passed directly to `onmessage` handler, 
not as `event.data` by [spec](https://html.spec.whatwg.org/multipage/comms.html#dom-messageevent-data):
```js
const WebSocketAsPromised = require('websocket-as-promised');
const WebSocket = require('ws');

const wsp = new WebSocketAsPromised('ws://example.com', {
  createWebSocket: url => new WebSocket(url),
  extractMessageData: event => event, // <- this is important
});

wsp.open()
  .then(() => wsp.send('message'))
  .then(() => wsp.close())
  .catch(e => console.error(e));
```

## Sending raw data
To send raw data use `.send()` method:
```js
wsp.send('foo');
```
To handle raw data from server use `.onMessage` channel:
```js
wsp.onMessage.addListener(data => console.log(data));
```

## Sending JSON
To send JSON you should define `packMessage / unpackMessage` options:
```js
const wsp = new WebSocketAsPromised(wsUrl, {
  packMessage: data => JSON.stringify(data),
  unpackMessage: data => JSON.parse(data)
});
```
To send data use `.sendPacked()` method passing json as parameter:
```js
wsp.sendPacked({foo: 'bar'});
```
To read unpacked data from received message you can subscribe to `onUnpackedMessage` channel:
```js
wsp.onUnpackedMessage.addListener(data => console.log(data.status));
```

## Sending binary
Example of sending `Uint8Array`:
```js
const wsp = new WebSocketAsPromised(wsUrl, {
    packMessage: data => (new Uint8Array(data)).buffer,
    unpackMessage: data => new Uint8Array(data),
});

wsp.open()
  .then(() => wsp.sendPacked([1, 2, 3]))
  .then(() => wsp.close())
  .catch(e => console.error(e));
```

## Sending requests
*websocket-as-promised* provides simple request-response mechanism ([JSON RPC](https://www.jsonrpc.org)). 
Method `.sendRequest()` sends message with unique `requestId` and returns promise. 
That promise get resolved when response message with the same `requestId` comes. 
For reading/setting `requestId` from/to message there are two functions defined in options `attachRequestId / extractRequestId`:
```js
const wsp = new WebSocketAsPromised(wsUrl, {
  packMessage: data => JSON.stringify(data),
  unpackMessage: data => JSON.parse(data),
  attachRequestId: (data, requestId) => Object.assign({id: requestId}, data), // attach requestId to message as `id` field
  extractRequestId: data => data && data.id,                                  // read requestId from message `id` field
});

wsp.open()
 .then(() => wsp.sendRequest({foo: 'bar'})) // actually sends {foo: 'bar', id: 'xxx'}, because `attachRequestId` defined above
 .then(response => console.log(response));  // waits server message with corresponding requestId: {id: 'xxx', ...}
```
By default `requestId` value is auto-generated, but you can set it manually:
```js
wsp.sendRequest({foo: 'bar'}, {requestId: 42});
```

> Note: you should implement yourself attaching `requestId` on server side.

## API

### Classes

WebSocketAsPromised

### Typedefs

Options : Object

### WebSocketAsPromised
**Kind**: global class  

* [WebSocketAsPromised](#WebSocketAsPromised)
    * [new WebSocketAsPromised(url, [options])](#new_WebSocketAsPromised_new)
    * [.ws](#WebSocketAsPromised+ws) ⇒ WebSocket
    * [.url](#WebSocketAsPromised+url) ⇒ String
    * [.isOpening](#WebSocketAsPromised+isOpening) ⇒ Boolean
    * [.isOpened](#WebSocketAsPromised+isOpened) ⇒ Boolean
    * [.isClosing](#WebSocketAsPromised+isClosing) ⇒ Boolean
    * [.isClosed](#WebSocketAsPromised+isClosed) ⇒ Boolean
    * [.onOpen](#WebSocketAsPromised+onOpen) ⇒ Channel
    * [.onSend](#WebSocketAsPromised+onSend) ⇒ Channel
    * [.onMessage](#WebSocketAsPromised+onMessage) ⇒ Channel
    * [.onUnpackedMessage](#WebSocketAsPromised+onUnpackedMessage) ⇒ Channel
    * [.onResponse](#WebSocketAsPromised+onResponse) ⇒ Channel
    * [.onClose](#WebSocketAsPromised+onClose) ⇒ Channel
    * [.onError](#WebSocketAsPromised+onError) ⇒ Channel
    * [.open()](#WebSocketAsPromised+open) ⇒ Promise.<Event>
    * [.sendRequest(data, [options])](#WebSocketAsPromised+sendRequest) ⇒ Promise
    * [.sendPacked(data)](#WebSocketAsPromised+sendPacked)
    * [.send(data)](#WebSocketAsPromised+send)
    * [.waitUnpackedMessage(predicate, [options])](#WebSocketAsPromised+waitUnpackedMessage) ⇒ Promise
    * [.close([code], [reason])](#WebSocketAsPromised+close) ⇒ Promise.<Event>
    * [.removeAllListeners()](#WebSocketAsPromised+removeAllListeners)

#### new WebSocketAsPromised(url, [options])
Constructor. Unlike original WebSocket it does not immediately open connection.
Please call `open()` method to connect.

| Param | Type | Description |
| --- | --- | --- |
| url | String | WebSocket URL |
| [options] | [Options](#Options) |  |

#### wsp.ws ⇒ WebSocket
Returns original WebSocket instance created by `options.createWebSocket`.

**Kind**: instance property of [WebSocketAsPromised](#WebSocketAsPromised)  

#### wsp.url ⇒ String
Returns WebSocket url.

**Kind**: instance property of [WebSocketAsPromised](#WebSocketAsPromised)  

#### wsp.isOpening ⇒ Boolean
Is WebSocket connection in opening state.

**Kind**: instance property of [WebSocketAsPromised](#WebSocketAsPromised)  

#### wsp.isOpened ⇒ Boolean
Is WebSocket connection opened.

**Kind**: instance property of [WebSocketAsPromised](#WebSocketAsPromised)  

#### wsp.isClosing ⇒ Boolean
Is WebSocket connection in closing state.

**Kind**: instance property of [WebSocketAsPromised](#WebSocketAsPromised)  

#### wsp.isClosed ⇒ Boolean
Is WebSocket connection closed.

**Kind**: instance property of [WebSocketAsPromised](#WebSocketAsPromised)  

#### wsp.onOpen ⇒ Channel
Event channel triggered when connection is opened.

**Kind**: instance property of [WebSocketAsPromised](#WebSocketAsPromised)  
**See**: https://vitalets.github.io/chnl/#channel  
**Example**  
```js
wsp.onOpen.addListener(() => console.log('Connection opened'));
```


#### wsp.onSend ⇒ Channel
Event channel triggered every time when message is sent to server.

**Kind**: instance property of [WebSocketAsPromised](#WebSocketAsPromised)  
**See**: https://vitalets.github.io/chnl/#channel  
**Example**  
```js
wsp.onSend.addListener(data => console.log('Message sent', data));
```


#### wsp.onMessage ⇒ Channel
Event channel triggered every time when message received from server.

**Kind**: instance property of [WebSocketAsPromised](#WebSocketAsPromised)  
**See**: https://vitalets.github.io/chnl/#channel  
**Example**  
```js
wsp.onMessage.addListener(message => console.log(message));
```


#### wsp.onUnpackedMessage ⇒ Channel
Event channel triggered every time when received message is successfully unpacked.
For example, if you are using JSON transport, the listener will receive already JSON parsed data.

**Kind**: instance property of [WebSocketAsPromised](#WebSocketAsPromised)  
**See**: https://vitalets.github.io/chnl/#channel  
**Example**  
```js
wsp.onUnpackedMessage.addListener(data => console.log(data.foo));
```


#### wsp.onResponse ⇒ Channel
Event channel triggered every time when response to some request comes.
Received message considered a response if requestId is found in it.

**Kind**: instance property of [WebSocketAsPromised](#WebSocketAsPromised)  
**See**: https://vitalets.github.io/chnl/#channel  
**Example**  
```js
wsp.onResponse.addListener(data => console.log(data));
```


#### wsp.onClose ⇒ Channel
Event channel triggered when connection closed.
Listener accepts single argument `{code, reason}`.

**Kind**: instance property of [WebSocketAsPromised](#WebSocketAsPromised)  
**See**: https://vitalets.github.io/chnl/#channel  
**Example**  
```js
wsp.onClose.addListener(event => console.log(`Connections closed: ${event.reason}`));
```


#### wsp.onError ⇒ Channel
Event channel triggered when by Websocket 'error' event.

**Kind**: instance property of [WebSocketAsPromised](#WebSocketAsPromised)  
**See**: https://vitalets.github.io/chnl/#channel  
**Example**  
```js
wsp.onError.addListener(event => console.error(event));
```


#### wsp.open() ⇒ Promise.<Event>
Opens WebSocket connection. If connection already opened, promise will be resolved with "open event".

**Kind**: instance method of [WebSocketAsPromised](#WebSocketAsPromised)  

#### wsp.sendRequest(data, [options]) ⇒ Promise
Performs request and waits for response.

**Kind**: instance method of [WebSocketAsPromised](#WebSocketAsPromised)  

| Param | Type | Default |
| --- | --- | --- |
| data | \* |  | 
| [options] | Object |  | 
| [options.requestId] | String \| Number | <auto-generated> | 
| [options.timeout] | Number | 0 | 

#### wsp.sendPacked(data)
Packs data with `options.packMessage` and sends to the server.

**Kind**: instance method of [WebSocketAsPromised](#WebSocketAsPromised)  

| Param | Type |
| --- | --- |
| data | \* | 

#### wsp.send(data)
Sends data without packing.

**Kind**: instance method of [WebSocketAsPromised](#WebSocketAsPromised)  

| Param | Type |
| --- | --- |
| data | String \| Blob \| ArrayBuffer | 

#### wsp.waitUnpackedMessage(predicate, [options]) ⇒ Promise
Waits for particular message to come.

**Kind**: instance method of [WebSocketAsPromised](#WebSocketAsPromised)  

| Param | Type | Default | Description |
| --- | --- | --- | --- |
| predicate | function |  | function to check incoming message. |
| [options] | Object |  |  |
| [options.timeout] | Number | 0 |  |
| [options.timeoutError] | Error |  |  |

**Example**  
```js
const response = await wsp.waitUnpackedMessage(data => data && data.foo === 'bar');
```


#### wsp.close([code], [reason]) ⇒ Promise.<Event>
Closes WebSocket connection. If connection already closed, promise will be resolved with "close event".

**Kind**: instance method of [WebSocketAsPromised](#WebSocketAsPromised)  

| Param | Type | Default | Description |
| --- | --- | --- | --- |
| [code] | number | 1000 | A numeric value indicating the status code. |
| [reason] | string |  | A human-readable reason for closing connection. |

#### wsp.removeAllListeners()
Removes all listeners from WSP instance. Useful for cleanup.

**Kind**: instance method of [WebSocketAsPromised](#WebSocketAsPromised)  

### Options : Object
**Kind**: global typedef  
**Defaults**: please see [options.js](https://github.com/vitalets/websocket-as-promised/blob/master/src/options.js)  
**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| [createWebSocket] | function | url => new WebSocket(url) | custom function for WebSocket construction. |
| [packMessage] | function | noop | packs message for sending. For example, `data => JSON.stringify(data)`. |
| [unpackMessage] | function | noop | unpacks received message. For example, `data => JSON.parse(data)`. |
| [attachRequestId] | function | noop | injects request id into data. For example, `(data, requestId) => Object.assign({requestId}, data)`. |
| [extractRequestId] | function | noop | extracts request id from received data. For example, `data => data.requestId`. |
| [extractMessageData] | function | event => event.data | extracts data from event object. |
| timeout | Number | 0 | timeout for opening connection and sending messages. |
| connectionTimeout | Number | 0 | special timeout for opening connection, by default equals to `timeout`. |

## Changelog
Please see [CHANGELOG.md](CHANGELOG.md).

## License
MIT @ [Vitaliy Potapov](https://github.com/vitalets)

* * *
If you love :heart: JavaScript and would like to track new trending repositories, 
have a look on vitalets/github-trending-repos (https://github.com/vitalets/github-trending-repos).

[Promise]: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise
[WebSocket]: https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API
