JSteunou / webstomp-client

Stomp client over websocket for browsers
Apache License 2.0
299 stars 59 forks source link

expose a function to return the readyState of the socket #17

Open mschipperheyn opened 8 years ago

mschipperheyn commented 8 years ago

It's useful to be able to tell whether the socket is active, opening, etc.

WebSocket exposes a readyState variable for this. It would be nice to expose it through the api

JSteunou commented 8 years ago

Or maybe just adding a way to get the websocket object underneath webstomp-client. This will avoid to add tons of abstraction just to mimic websocket API.

mschipperheyn commented 8 years ago

I'm realizing that I need to create subscribe/unsubscribes outside of the connect callback. So, either I need to tell whether the connection is open or not or, ideally, I could just call the connect method sa often if I wanted, with the socket knowing if it was open or not and immediately returning against the callback with the already open connection or, creating a new connection.

JSteunou commented 8 years ago

What I do in my app is maintening a list of subscription. If I'm already connected, I call the client subscribe method, if I'm not, I store the subscription for later.

Maybe that logic can be a part of the webstomp client.

mschipperheyn commented 8 years ago

Could you share some of that code as an example. How do you know if you're already connected?

JSteunou commented 8 years ago

Sorry I cannot. I just store a simple boolean flag that I switch to true once connected.

mschipperheyn commented 8 years ago

I think the websocket.readyState will provider the info

On Tue, Jul 5, 2016 at 6:22 PM Jérôme Steunou notifications@github.com wrote:

Sorry I cannot. I just store a simple boolean flag that I switch to true once connected.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/JSteunou/webstomp-client/issues/17#issuecomment-230607223, or mute the thread https://github.com/notifications/unsubscribe/AAXfv0FqCxF1kffR3VGYP1LpQ13erB_Aks5qSsssgaJpZM4I8ylu .

Kind regards,

Marc M.Schipperheyn

JSteunou commented 8 years ago

Then why not use stomp.ws.readyState ?

stomp client already exposes the websocket even if it's not so much documented. But using readyState is not comfortable because you have to check its value regularly, where events like onmessage or onerror are triggered as it happens.

mschipperheyn commented 8 years ago

yeah, ok. the ideal would be for calling CONNECT to be indemnipotent, so subscribes and sends could always run inside the safe context of the callback.

JSteunou commented 8 years ago

Pull request are welcome if you wanna share your idea 😉 Le 7 juil. 2016 00:53, "Marc Schipperheyn" notifications@github.com a écrit :

yeah, ok. the ideal would be for calling CONNECT to be indemnipotent, so subscribes and sends could always run inside the safe context of the callback.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/JSteunou/webstomp-client/issues/17#issuecomment-230931813, or mute the thread https://github.com/notifications/unsubscribe/ACNyt3dBNsUj3lsMMjkgVMCSQJg6omp1ks5qTDH4gaJpZM4I8ylu .

mschipperheyn commented 8 years ago

Well, what I ended up implementing is a buffering solution, backed by a reconnecting websocket. Not sure if it's a good idea. WDYT?

const socket = class Socket{

  constructor(){}

  createAndConnect(sessionToken, rememberMeToken, onConnectSuccess, onConnectFailure){
    this.createClient(sessionToken, rememberMeToken);
    this.connect(onConnectSuccess, onConnectFailure);
  }

  createClient(sessionToken, authToken){

    console.info(`Connecting to WebSocket: ${config.getWebsocketUrl()}`);

    this.sendBuffer = [];
    this.subscribeBuffer = [];
    this.unsubscribeBuffer = [];
    this.bufferSize = 10;

    let WS = new ReconnectingWebSocket(config.getWebsocketUrl(),{},this.getCookieHeader(sessionToken, authToken), (func) => {
      func();
    });

    WS.debug=config.debugWebsocket;

    this.ws = webstomp.over(
      WS,
      {
        debug:false
      }
    );
  }

  getCookieHeader(sessionToken, authToken){
    if(sessionToken || authToken)
      return {
        'Cookie': ATTR_SEND_SESSIONID + '=' + sessionToken + ';' + ATTR_SEND_REMEMBERME_TOKEN + '=' + authToken
      };

    return {};
  }

  getClient(){
    return this.ws? this.ws : new Error("No websocket present. Call createAndConnect first");
  }

  connect(onConnectSuccess, onConnectFailure){
    var that = this;

    this.headers = {};

    this.ws.connect(this.headers, function(frame){

      onConnectSuccess(frame);

      that.processBuffer.bind(that)();

    }, onConnectFailure);
  }

  disconnect(){
    this.ws.disconnect();
  }

  send(destination, body, bufferable = true){
    if(this.readyState() === WS_STATES.OPEN){
      this.ws.send(destination,body, this.headers);
      return;
    }

    if(bufferable && this.sendBuffer.length <= this.bufferSize){
      this.sendBuffer.push({
        destination,
        body
      });
    }else{
      throw new Error('Trying to send message on closed socket and no buffer available', destination, this.readyState());
    }
  }

  subscribe(destination, callback, bufferable = true){
    if(this.readyState() === WS_STATES.OPEN){
      this.ws.subscribe(destination, (message) => {

        message.ack();

        callback(message);

      }, this.headers);
      return;
    }

    if(bufferable && this.subscribeBuffer.length <= this.bufferSize){

      if(this.subscribeBuffer.filter((el) => el.destination === destination).length === 0)
        this.subscribeBuffer.push({
          destination,
          callback
        });
    }else{
      throw new Error(`Trying to subscribe on closed socket and no buffer available: ${destination}, readyState: ${this.readyState()}, buffer length: ${this.subscribeBuffer.length}`);
    }
  }

  unsubscribe(destination, bufferable = true){
    if(this.readyState() === WS_STATES.OPEN){
      this.ws.unsubscribe(destination);
      return;
    }

    if(bufferable && this.unsubscribeBuffer.length <= this.bufferSize){
      if(this.unsubscribeBuffer.filter((el) => el.destination === destination).length === 0)
        this.unsubscribeBuffer.push({
          destination
        });
    }else{
      throw new Error(`Trying to unsubscribe on closed socket and no buffer available: ${destination}, readyState: ${this.readyState()}, buffer length: ${this.unsubscribeBuffer.length}`);
    }
  }

  begin(transactionId){
    return this.ws.begin(transactionId);
  }

  commit(tx){
    tx.commit();
  }

  abort(tx){
    tx.abort();
  }

  sessionId(){
    return this.ws.sessionId;
  }

  readyState(){
    return this.ws.ws.readyState;
  }

  processBuffer(){
    console.log('Processing websocket buffer', this.sendBuffer.length);

    //Process unsubscribe first, in case a new subscribe came in for the to be unsubscribed endpoint
    for(let action of this.unsubscribeBuffer){
      this.unsubscribe(action.destination);
    }

    for(let action of this.subscribeBuffer){
      this.subscribe(action.destination, action.callback);
    }

    for(let action of this.sendBuffer){
      this.send(action.destination, action.body, false);
    }

    this.sendBuffer = [];
    this.subscribeBuffer = [];
    this.unsubscribeBuffer = [];
  }

}

export {
  socket as default
}
JSteunou commented 8 years ago

A PR with a clean diff is more readable

mschipperheyn commented 8 years ago

Well, this is just a suggestion. It's not a change to your code