Code Monkey home page Code Monkey logo

websocket-as-promised's Introduction

websocket-as-promised

websocket-as-promised logo

Actions Status Known Vulnerabilities npm license

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

Example

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

  • global Promise constructor

Installation

npm i websocket-as-promised --save

Usage in browser

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:

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

Here you should use W3C compatible client - W3CWebSocket:

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

Here it is important to define extractMessageData option as event data are passed directly to onmessage handler, not as event.data by spec:

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:

wsp.send('foo');

To handle raw data from server use .onMessage channel:

wsp.onMessage.addListener(data => console.log(data));

Sending JSON

To send JSON you should define packMessage / unpackMessage options:

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:

wsp.sendPacked({foo: 'bar'});

To read unpacked data from received message you can subscribe to onUnpackedMessage channel:

wsp.onUnpackedMessage.addListener(data => console.log(data.status));

Sending binary

Example of sending Uint8Array:

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). 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:

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:

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

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

wsp.ws ⇒ WebSocket

Returns original WebSocket instance created by options.createWebSocket.

Kind: instance property of WebSocketAsPromised

wsp.url ⇒ String

Returns WebSocket url.

Kind: instance property of WebSocketAsPromised

wsp.isOpening ⇒ Boolean

Is WebSocket connection in opening state.

Kind: instance property of WebSocketAsPromised

wsp.isOpened ⇒ Boolean

Is WebSocket connection opened.

Kind: instance property of WebSocketAsPromised

wsp.isClosing ⇒ Boolean

Is WebSocket connection in closing state.

Kind: instance property of WebSocketAsPromised

wsp.isClosed ⇒ Boolean

Is WebSocket connection closed.

Kind: instance property of WebSocketAsPromised

wsp.onOpen ⇒ Channel

Event channel triggered when connection is opened.

Kind: instance property of WebSocketAsPromised
See: https://vitalets.github.io/chnl/#channel
Example

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
See: https://vitalets.github.io/chnl/#channel
Example

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
See: https://vitalets.github.io/chnl/#channel
Example

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
See: https://vitalets.github.io/chnl/#channel
Example

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
See: https://vitalets.github.io/chnl/#channel
Example

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
See: https://vitalets.github.io/chnl/#channel
Example

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
See: https://vitalets.github.io/chnl/#channel
Example

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

wsp.sendRequest(data, [options]) ⇒ Promise

Performs request and waits for response.

Kind: instance method of 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

Param Type
data *

wsp.send(data)

Sends data without packing.

Kind: instance method of WebSocketAsPromised

Param Type
data String | Blob | ArrayBuffer

wsp.waitUnpackedMessage(predicate, [options]) ⇒ Promise

Waits for particular message to come.

Kind: instance method of WebSocketAsPromised

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

Example

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

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

Options : Object

Kind: global typedef
Defaults: please see 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.

License

MIT @ Vitaliy Potapov

* * *
If you love ❤️ JavaScript and would like to track new trending repositories,
have a look on vitalets/github-trending-repos.

websocket-as-promised's People

Contributors

ivosh avatar steffen-4s1 avatar vitalets avatar yandevde avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

websocket-as-promised's Issues

Feature request: Improve error handling (error thrown from `open()` should contain the actual error, e.g. `ECONNREFUSED`)

Right now the only way to detect if this library throws an error on connection (as opposed to something else) is to check for the string err.message.startsWith('WebSocket closed with reason:').

For example this error is thrown on an unsuccessful await wsp.open() call:

RWS> addListeners
RWS> error event connect ECONNREFUSED ::1:3456
Error: WebSocket closed with reason:  (1000).
    at WebSocketAsPromised._handleClose (/Users/user/Projects/web/node_modules/.pnpm/[email protected]/node_modules/websocket-as-promised/src/index.js:408:19)
    at listener (/Users/user/Projects/web/node_modules/.pnpm/[email protected]/node_modules/websocket-as-promised/src/index.js:340:64)
    at ReconnectingWebSocket._callEventListener (/Users/user/Projects/web/node_modules/.pnpm/[email protected]/node_modules/reconnecting-websocket/dist/reconnecting-websocket-cjs.js:557:13)
    at /Users/user/Projects/web/node_modules/.pnpm/[email protected]/node_modules/reconnecting-websocket/dist/reconnecting-websocket-cjs.js:197:79
    at Array.forEach (<anonymous>)
    at ReconnectingWebSocket._handleClose (/Users/user/Projects/web/node_modules/.pnpm/[email protected]/node_modules/reconnecting-websocket/dist/reconnecting-websocket-cjs.js:197:36)
    at ReconnectingWebSocket._disconnect (/Users/user/Projects/web/node_modules/.pnpm/[email protected]/node_modules/reconnecting-websocket/dist/reconnecting-websocket-cjs.js:540:18)
    at ReconnectingWebSocket._handleError (/Users/user/Projects/web/node_modules/.pnpm/[email protected]/node_modules/reconnecting-websocket/dist/reconnecting-websocket-cjs.js:180:19)
    at callListener (/Users/user/Projects/web/node_modules/.pnpm/[email protected]_nn4cfacdngcg7bs47xs3zpla7u/node_modules/ws/lib/event-target.js:290:14)
    at WebSocket.onError (/Users/user/Projects/web/node_modules/.pnpm/[email protected]_nn4cfacdngcg7bs47xs3zpla7u/node_modules/ws/lib/event-target.js:230:9)
    at WebSocket.emit (node:events:513:28)
    at emitErrorAndClose (/Users/user/Projects/web/node_modules/.pnpm/[email protected]_nn4cfacdngcg7bs47xs3zpla7u/node_modules/ws/lib/websocket.js:1016:13)
    at ClientRequest.<anonymous> (/Users/user/Projects/web/node_modules/.pnpm/[email protected]_nn4cfacdngcg7bs47xs3zpla7u/node_modules/ws/lib/websocket.js:864:5)
    at ClientRequest.emit (node:events:513:28)
    at Socket.socketErrorListener (node:_http_client:502:9)
    at Socket.emit (node:events:513:28)
    at emitErrorNT (node:internal/streams/destroy:151:8)
    at emitErrorCloseNT (node:internal/streams/destroy:116:3)
    at process.processTicksAndRejections (node:internal/process/task_queues:82:21)

Having access to the raw error would be useful for error handling purposes (e.g. being able to see err.code is ECONNREFUSED.

Additionally, the err.reason property can be empty, and as you can see from the above example it has extra whitespace added to the error string (which isn't dev-friendly). This will require a rewrite to _handleClose and _handleError handlers most likely (?)

Reference:

const error = new Error(`WebSocket closed with reason: ${event.reason} (${event.code}).`);

How to set header of the ws connection

I'm adding authentication to ws.

In original websocket:
new WebSocket("ws://www.example.com/socketserver", ["access_token", "3gn11Ft0Me8lkqqW2/5uFQ="]);

I'v searched the APIs of README page but found nothing.
How to use websocket-as-promised to pass params to ws connection.

Provide unpacked sources by default to reduce bundle size in final apps

Problem
Currently package.main points to dist/index.js. It means when I build my app with websocket-as-promised - I can get duplicated dependencies (and definitely duplicated webpack-runtime)

Solution

  • point package.main to src/index.js. But build bundle as well. Bundle can be included as require('websocket-as-promised/dist').
  • replace dependency promise-controller (provided also as bundled) with more universal promised-map
  • use rollup as bundler

Error: TS2345: Argument of type W3CWebSocket is not assignable to parameter of type 'Options'

Thanks for the library it's really helpful. But I got an error on the built typescript project.
The problem is that I use W3CWebSocket in the createWebSocket property but in the Options interface I should return WebSocket type.

const wspParams = {
  createWebSocket: (url: string) => new W3CWebSocket(url),
}
declare interface Options {
    createWebSocket?: (url: string) => WebSocket;
}

https://github.com/vitalets/websocket-as-promised/blob/master/types/options.d.ts#L5

TS2345: Argument of type '{ createWebSocket: (url: string) => W3CWebSocket; packMessage: (data: object) => string; unpackMessage: (data: string) => any; attachRequestId: (data: object, requestId: string) => { id: string; }; extractRequestId: (data: { id: string;}) => string; timeout: number; }' is not assignable to parameter of type 'Options'.

Support async operations in unpackMessage()

I've been experimenting with this library in the browser,
but run into one issue I am not sure how to resolve.

const wsp = new WebSocketAsPromised(wsUrl, {
  packMessage: data => JSON.stringify(data),
 unpackMessage: data => JSON.parse(data)
});

For the unpackMessage function, in the browser, I receive a Blob, but I don't think I can get the Blob data before returning from the function. When I tried JSON.parse it just returned undefined.
Maybe data is not a blob in Node, but it seems to be in Chrome.
Let me know if an example would help.

Error: WebSocket closed with reason: (1006).

Here's how I implement it:

  componentDidMount(){
    document.addEventListener("keydown", this.handleKeyDown);
    (async () => {
      try {
        await this.wsp.open();
        this.wsp.onmessage = (evt) => {

           let data = evt.data;
           let userInput = this.state.userInput;
           let cleanedUserInput = data.replace(userInput, "");
           this.setState({
             dataFromServer: cleanedUserInput
           }, () => {
             this.props.displayPrediction(cleanedUserInput)
           })
        }
      }catch(e) {
        console.error(e);
      }
    })();
  };

and:

  sendDataToServer = () => {
    if(this.state.userInputLength < 7){
        return this.handleErrorMessage();
    }
    let dataToSend = this.state.userInput;

 (async () => {
      try {
        await this.wsp.open();
        this.wsp.send(dataToSend);

      } catch(e) {
        console.error(e);
      }
    })();

    this.props.togglingLoadingSection(this.state.userInput);
    return this.resetPrediction();
  };

When the socket operates, I get the following:

index.js:1 Error: WebSocket closed with reason:  (1006).
    at WebSocketAsPromised._handleClose (index.js:353)
    at WebSocket.listener (index.js:311)

Add onSend channel

This is useful during development to log what is actually sent to server.

Separate timeout for opening connection

Currently there is only one timeout option for all requests to server.
It looks reasonable to have separate timeout for connection as it can take more time.

Is there a good way to use this in vanilla JS?

We run a Go backend with an HTML+JS+JQuery frontend. Currently we're using reconnecting-websocket for communication, but having request and response in different functions complicates things greatly, so we were looking for a library that wraps it in a way so we can use async/await. I have no idea how Node.js works, and I'm 99.9% sure that we don't want to depend on it.

bug: build in typescript Found 4 errors.

used in typescript:

  import WebSocketAsPromised from 'websocket-as-promised';
  
  const wsp = new WebSocketAsPromised(this.options.address, {
              createWebSocket: url => new WebSocket(url),
              extractMessageData: event => event, // <- this is important
              packMessage: data => JSON.stringify(data),
              unpackMessage: data => {
                  if (Helper.isJSONStr(<string>data)) {
                      return JSON.parse(<string>data);
                  } else {
                      return data;
                  }
              },
              attachRequestId: (data, requestId) => Object.assign({ id: requestId }, data), // attach requestId to message as `id` field
              extractRequestId: data => data && data.id,
          });

        return wsp.open().then(() => {
            this.service = wsp;
            return wsp;
        });

run build tsc
throw errors:

  node_modules/websocket-as-promised/types/index.d.ts:9:9 - error TS2304: Cannot find name 'WebSocket'.
  
  9     ws: WebSocket;
            ~~~~~~~~~
  
  node_modules/websocket-as-promised/types/index.d.ts:21:25 - error TS2304: Cannot find name 'Event'.
  
  21     open: () => Promise<Event>;
                             ~~~~~
  
  node_modules/websocket-as-promised/types/index.d.ts:26:26 - error TS2304: Cannot find name 'CloseEvent'.
  
  26     close: () => Promise<CloseEvent>;
                              ~~~~~~~~~~
  
  node_modules/websocket-as-promised/types/options.d.ts:5:40 - error TS2304: Cannot find name 'WebSocket'.
  
  5     createWebSocket?: (url: string) => WebSocket;
                                           ~~~~~~~~~

Help with transitioning from different library

I am having a hard time coming from react-use-websocket. Do I need to keep this connection open as I am doing previously? Or is that where the async part comes into play? I am not sure on how to do this. It would be great if someone could help me transition my logic to use this library.

`
import useWebSocket, { ReadyState } from 'react-use-websocket';

const SOCKET_URL = '';

let timestamp = new Date().getTime();
const UseWebSocket = ({ webSocketRef }: { webSocketRef: any }) => {
  webSocketRef.current = useWebSocket(SOCKET_URL, {
    retryOnError: true,
    reconnectInterval: 100000,
  });

  const { sendJsonMessage, lastJsonMessage, readyState } = webSocketRef.current;
  const data = useSelector(getEditForecastJSONSelector);

  const prevData = React.useRef<any>();

  const dispatch = useDispatch();

  const onSetDataReceived = (response: any) => {
    const currentTimestamp = new Date().getTime();
    const diff = currentTimestamp - timestamp;
    if (diff > 200) {
      dispatch(actions.onForeCastDataReceived(response));
      dispatch(actions.onIsLoading(false));
      timestamp = new Date().getTime();
    }
  };

  React.useEffect(() => {
    if (readyState === ReadyState.OPEN) {
      if (!isEqual(prevData.current, data) && data.length > 0) {
        data.forEach((item) => {
          if (item.data) {
            item.data.forEach((x) => {
              x.forecasts.forEach((f) => {
                const payload: SocketPayload = getSocketPayload({ item, f });
                sendJsonMessage(payload, false);
              });
            });
          }
        });
      }
      prevData.current = data;
    }
  }, [
    dispatch,
    data,
    sendJsonMessage,
    readyState,
  ]);

  React.useEffect(() => {
    if (lastJsonMessage !== null) {
      onSetDataReceived(lastJsonMessage);
    } else dispatch(actions.onIsLoading(false));
  }, [lastJsonMessage, onSetDataReceived]);

  return webSocketRef.current;
};

export default UseWebSocket;
`

Current Logic I am trying to get to work.

`const SOCKET_URL = '';

let timestamp = new Date().getTime();
const UseWebSocket = ({ webSocketRef }: { webSocketRef: any }) => {
  webSocketRef.current = new WebSocketAsPromised(SOCKET_URL, {
    packMessage: data => JSON.stringify(data),
    unpackMessage: data => JSON.parse(String(data)),
  });

  const data = useSelector(getEditForecastJSONSelector);

  const prevData = React.useRef<any>();

  const dispatch = useDispatch();

  const onSetDataReceived = (response: any) => {
    const currentTimestamp = new Date().getTime();
    const diff = currentTimestamp - timestamp;
    if (diff > 200) {
      dispatch(actions.onForeCastDataReceived(response));
      dispatch(actions.onIsLoading(false));
      timestamp = new Date().getTime();
    }
  };

  React.useEffect(() => {
    // (async () => {
    //   try {
    //     await webSocketRef.current.open();
    //   } catch (e) {
    //     console.error(e);
    //   }
    // })();
  }, []);

  React.useEffect(() => {
    const run = async () => {
      if (!isEqual(prevData.current, data) && data.length > 0) {
        for (const item of data) {
          if (item.data) {
            for (const x of item.data) {
              for (const f of x.forecasts) {
                const payload: SocketPayload = getSocketPayload({ item, f });
                try {
                  webSocketRef.current.onError.addListener((event: any) => console.error(event));
                  webSocketRef.current.onUnpackedMessage.addListener((res: any) => {
                    console.log(res);
                    onSetDataReceived(res);
                  });
                  await webSocketRef.current.open();
                  await webSocketRef.current.sendPacked(payload);
                } catch (e) {
                  console.error(e);
                } finally {
                  // await webSocketRef.current.close();
                }
              }
            }
          }
        }
      }
      prevData.current = data;
    };
    run();
  }, [
    dispatch,
    data,
    onSetDataReceived,
  ]);

  React.useEffect(() => {
    // if (lastJsonMessage !== null) {
    //   onSetDataReceived(lastJsonMessage);
    // } else dispatch(actions.onIsLoading(false));
  }, [onSetDataReceived]);

  return webSocketRef.current;
};

export default UseWebSocket;`

How to use CBOR to pack/unpack messages?

I would like to use CBOR to pack/unpack messages in a plain HTML/JS application.

I use spaceify/cbor-js (a fork of paroga/cbor-js)

<script src="https://cdn.jsdelivr.net/gh/spaceify/cbor-js@master/cbor.js"></script>

then in my JS code:

this.websocket = new WebSocketAsPromised(
    `ws://${this.host}:${this.port}`,
    {
        packMessage: data => CBOR.encode(data),
        unpackMessage: data => CBOR.decode(data),
        // attach requestId to message as `id` field
        attachRequestId: (data, requestId) => Object.assign({id: requestId}, data),
        // read requestId from message `id` field
        extractRequestId: data => data && data.id,
    }
);

but I have an error (TypeError: First argument to DataView constructor must be an ArrayBuffer) because unpackMessage calls CBOR.decode(data) with data being an object of type Blob.

I tried to change unpackMessage to use Blob.arrayBuffer():

unpackMessage: data => CBOR.encode(data.arrayBuffer())

but data.arrayBuffer() returns a Promise, however this:

unpackMessage: data => CBOR.encode(await data.arrayBuffer())

is a syntax error (Uncaught SyntaxError: missing ) after argument list).

Any idea how to fix this?

Can I use this repo to detect websocket http request header: sec-websocket-key?

Hello:
I need some code sample on how to detect WebSocket (wss) requests.
Basically, I can visit this URL with Chrome: https://www.websocket.org/echo.html
And click on “Connect” button to connect with the WebSocket server: wss://echo.websocket.org
Then type something in the message box, or using the default text in the message box “Rock it with HTML5 WebSocket”, then click on “Send” button, then I can see the messages on the “log” textbox, like this:
CONNECTED
SENT: Rock it with HTML5 WebSocket
RECEIVED: Rock it with HTML5 WebSocket
Then open Developer Tools from Chrome, on “Network” tab, I can see there is only one WS (WebSocket) request with the following headers:
Request URL:
wss://echo.websocket.org/?encoding=text

Request Method:
GET
Status Code:
101 Web Socket Protocol Handshake
I can use Chrome developer tools to convert the WebSocket request to Node.JS fetch:
fetch("wss://echo.websocket.org/?encoding=text", {
"headers": {
"accept-language": "en-US,en;q=0.9,fr;q=0.8,nl;q=0.7,zh-CN;q=0.6,zh;q=0.5,zh-TW;q=0.4",
"cache-control": "no-cache",
"pragma": "no-cache",
"sec-websocket-extensions": "permessage-deflate; client_max_window_bits",
"sec-websocket-key": "PVg+yYO5Q3EVgtPLLV2QXQ==",
"sec-websocket-version": "13"
},
"body": null,
"method": "GET",
"mode": "cors"
});
Let me know how I can use this repo to get all the headers, actually, there is only one header I need to get is: "sec-websocket-key": "PVg+yYO5Q3EVgtPLLV2QXQ=="
In my real-world job, I have to detect webscoket request from one specific web site. Using Chrome devtools, I can see that the web page uses rather complicated JavaScript to generate a websocket request, and sends it to the server. I want to write some kind of browser extension to detect the websocket request headers, actually, I only need this header: "sec-websocket-key".
Please advise,
Thanks,

Add getter for url

Currently url can be get via wsp.ws.url.
But if WebSocket is not opened, it will throw error because ws is null. So it is better to just return this._url from WSP instance instead of accessing ws.

Self-signed certs

Getting connection errors due to self-signed certs. Is their an option similar to ws that allows you to bypass the connection error?

const WebSocket = require('ws');
const ws = new WebSocket('wss://url',{rejectUnauthorized: false});

Feature request: Add support for lazy `send` (e.g. trigger `open` if `!isOpened`)

Right now the logic is to throw an error if the connection is not already opened when send is called (either directly or via sendPacked, sendRequest, etc).

https://github.com/vitalets/websocket-as-promised/blob/bc60644c935b69eba1284f5863d3d91099165bd0/src/index.js#L251-L255C4

It would be great to alter this behavior to support a lazy option (Boolean, and false by default). If true, then it would attempt to open the connection inside of send() function.

Add onError event channel

Currently adding error listener is possible only from original ws instance:

wsp.ws.addEventListener('error', listener);

There are two disadvantages:

  1. Adding listener is only possible after connect (like in original WebSocket)
  2. It is inconsistent with other event channels: onClose and onMessage

The solution is to add onError channel with following usage:

wsp.onError.addListener(e => console.error(e));

[Question] As a server usage or how to pass PORT to listen to?

Hello @vitalets,

Just out of curiosity, is it possible to use websocket-as-promised as a websocket server akin to ws module?

For example:
https://github.com/websockets/ws#simple-server

const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });

wss.on('connection', function connection(ws) {
  ws.on('message', function incoming(message) {
    console.log('received: %s', message);
  });
  ws.send('something');
});

If it's not can I submit PR that will introduce this functionality?
Thanks!

Formal LICENSE file

@vitalets Hey Vitaliy, this is an awesome looking library!
But I almost decided not to use it, because I did not see a license file.
After searching around I did see the MIT letters next to your name, but
I know if I show this to any company's legal team, they will most likely
say, "that's not sufficient evidence of a MIT license".

Would you accept a PR with the MIT license with your name on it, or
please consider adding one?
Some users may not find a LICENSE and avoid this package,
which is a shame, because it looks awesome to me.

Thanks,
Cameron

Tolerate already extracted data coming from on message handler from underlying websocket

Hi there,
I have to use ws package because it supports setting custom headers when opening a websocket, but it turns out that it doesn't cooperate well with the websocket-as-promised. ws implements all the needed methods but in the on('message', ... handler it doesn't pass the full event, but it passes the already extracted data property. Would you mind adding a little check of the incoming parameter? It's just a matter of changing

  _handleMessage(event) {
    const message = event.data;

to

  _handleMessage(event) {
    const message = typeof event === 'string' ? event : event.data;

in the index.js. It stays compatible with the already supported websocket implementations and, at the same time, tolerates the use of ws. I will be happy to send PR for this if you're willing to accept such a hack.

ReferenceError: WebSocket is not defined

I am getting following error while using this library on nodejs, I am new to this can help me to resolve this.

ReferenceError: WebSocket is not defined
at Object.createWebSocket (/node_modules/websocket-as-promised/dist/webpack:/src/options.js:34:20)
at WebSocketAsPromised._createWS (/node_modules/websocket-as-promised/dist/webpack:/src/index.js:291:30)
at /node_modules/websocket-as-promised/dist/webpack:/src/index.js:205:12
at PromiseController._callFn (/node_modules/websocket-as-promised/dist/webpack:/node_modules/promise-controller/lib/index.js:187:1)
at PromiseController.call (/node_modules/websocket-as-promised/dist/webpack:/node_modules/promise-controller/lib/index.js:62:1)
at WebSocketAsPromised.open (/node_modules/websocket-as-promised/dist/webpack:/src/index.js:203:26)

v0.10.0 causes runtime error when building with Create React App

I'm using websocket-as-promised in an ejected version of "create-react-app". When testing using the dev server, everything works fine. But when I build the project and then run it, I get an error "Uncaught TypeError: Super expression must either be null or a function, not undefined" and the websocket code fails to run.

Downgrading to version 0.9.0 fixes the problem.

I have not had a chance to try much debugging. I tried to change the Terser minification plugin option in my app to keep_fnames: true, going on advice from various Stack Overflow posts. This did not resolve the issue. I also tried disabled the Terser plugin in my app, and that resolved the issue.

Feature Suggestion: await new messages

I hope this package can enable us to do the following:

await ws.open();

while (true) {
  data = await ws.recv();
  ws.send(data);
}

This is actually not that trivial if we need to handle connection error without a wrapper.

failed to get response from server

Hi, I am using sendRequest() API, and always failed to get response even though I send it from server with same request id.

Here is the detailed code.

const ws = new WebSocketAsPromised('ws://127.0.0.1:8000/ws', {
          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, 
      });
ws.onMessage.addListener(message => console.log("received ", message));
ws.open();

// send request 
await ws.sendRequest({ task: 'move_ptp' }, { requestId: 1 })
.then((response) => { console.log("responsed"); console.log(response) });

console.log("finished");

I have a server running behind, sending response with same message after 1 second receiving request.

When I run the code I only see the log in onMessage, but not in promise, and I didn't see "finished".

I wonder, did I configure something wrong ? Or I didn't extract requestID from received message correctly ?

Bug: createWebSocker option prevents from using custom WebSocket types

When trying to create a new WebSocketAsPromised using TypeScript, compiler throws an error if your WebSocket object is coming from some library such WebSocket-Node

Code:

import WebSocketAsPromised from 'websocket-as-promised' 
import websocket from 'websocket'

const W3CWebSocket = websocket.w3cwebsocket

const ws = new WebSocketAsPromised('xxxx', {
  createWebSocket: url => new W3CWebSocket(url)
})

Error:

Type '(url: string) => w3cwebsocket' is not assignable to type '(url: string) => WebSocket'.
  Type 'w3cwebsocket' is missing the following properties from type 'WebSocket': addEventListener, removeEventListener, dispatchEvent

Cause (type/options.d.ts):

export = Options;

declare interface Options {
    createWebSocket?: (url: string) => WebSocket; <------------ maybe add | any (?)
    packMessage?: (data: any) => string | ArrayBuffer | Blob;
    unpackMessage?: (data: string | ArrayBuffer | Blob) => any;
    attachRequestId?: (data: any, requestId: string | number) => any;
    extractRequestId?: (data: any) => string | number | undefined;
    extractMessageData?: (event: any) => any;
    timeout?: number;
    connectionTimeout?: number;
}

Solution from client code

import WebSocketAsPromised from 'websocket-as-promised' 
import websocket from 'websocket'

const W3CWebSocket = websocket.w3cwebsocket

const ws = new WebSocketAsPromised('xxxx', {
  createWebSocket: url => new W3CWebSocket(url) as any
})

Add support for auto reconnect

It would be nice of websocket-as-promised had a support for auto-reconnect in case of the underlying websocket disconnect.
There are actually two use cases:

  • Reconnect after the underlying websocket got disconnected
  • Keep trying reconnect if the first initial connect attempt failed

I envision a need for the following configuration options:

  • reconnectInterval [ms]
  • reconnectAttempts
  • reconnectDecay (eventually)

Add onOpen channel

Although wsp.open() returns promise that gets resolved after connection established, it is useful to have .onOpen event channel for logging and development.
E.g.:

const wsp = new WebSocketAsPromised(wsUrl);

wsp.onOpen.addListener(() => console.log('connection opened'));

wsp.open()
  .catch(e => console.error(e));

When next release?

extractMessageData is not in the latest npm release 1.0.1 (for typescript declarations)

When can we expect a new release?

Support subprotocols without synchronized sequence ID

I want to use this library with a subprotocol where the client and server have their own sequence ID. Both peers should be able to send requests and receive corresponding responses.

The problem I encounter:
The subprotocol is based on request-response pattern.
Imagine both peers use their own sequenceId to number their requests and both peers start with sequenceId = 1.
So it is possible, that both peers send a request with the same sequence ID at the same time. Now the client considers the request of the server as the response of its request and the real response of the server is dismissed.

This happens because the assignment of messages is only based on the sequence ID.

Feature Request:
Give the caller the opportunity to save additional data to every request so the assignment of messages is not only based on the sequence ID but on additional data.

Example:

Request opcode Possible response opcodes
login ack, nak
getProperties properties, nak
... ...

If I send a login command and it gets the sequenceId = 34, I can save the possible response opcodes as additional data for that request and when I get a response matched by the sequenceId I can validate the response by checking if the response opcode matches ack or nak.

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
  isPossibleResponse: (data, additionalData) => additionalData.possibleResponses.includes(data.opcode) // <--- new parameter
});

wsp.sendRequest({foo: 'bar'},{requestId: 42, additionalData: {possibleResponses: ['ack', 'nak']}});

I worked a bit on this idea in the following fork: https://github.com/PMalte/websocket-as-promised/tree/feature/bidirectional-subprotocols

New method: removeAllListeners

If app can dynamically change WebSocket url and re-create WebSocketAsPromised instance we should remove all listeners from previous instance (to avoid unexpected events):

createWSP(url) {
    this._wsp = new WebSocketAsPromised(this._wsUrl);
    this._wsp.onMessage.addListener(data => console.log(data));
    this._wsp.onMessage.addListener(res => this._handleMessage(res));
    this._wsp.onClose.addListener(() => this._handleClose());
}

cleanup() {
   this._wsp.onMessage.removeAllListeners();
   this._wsp.onUnpackedMessage.removeAllListeners();
   this._wsp.onClose.removeAllListeners();
}

To reduce boilerplate in cleanup it would be great to make cleanup in one line:

this._wsp.removeAllListeners();

It will remove listeners from all possible channels:

  • onOpen
  • onMessage
  • onUnpackedMessage
  • onResponse
  • onSend
  • onClose
  • onError

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.