Giter VIP home page Giter VIP logo

stomp_dart_client's Introduction

Stomp Dart

This library provides an implementation for a STOMP client connecting to a remote server. It should work for both pure dart and flutter.

Usage

Initialize

The client gets created the specified config, please see the Config section to see all available options

StompClient client = StompClient(
    config: StompConfig(
        url: 'wss://yourserver',
        onConnect: onConnectCallback
    )
);

The connect callback should be used to make sure that we are actually connected before we subscribe or send messages

void onConnectCallback(StompFrame connectFrame) {
    // client is connected and ready
}

Connect

client.activate();

Subscribe

client.subscribe(destination: '/foo/bar', headers: {}, callback: (frame) {
    // Received a frame for this subscription
    print(frame.body);
})

Ack/Nack

client.ack(id: message-id, headers: headers);

client.nack(id: message-id, headers: headers);

Unsubscribe

client.subscribe(...) returns a function which can be called with an optional map of headers

dynamic unsubscribeFn = client.subscribe(destination: '/foo/bar', headers: {}, callback: (frame) {
    // Received a frame for this subscription
    print(frame.body);
})
...
unsubscribeFn(unsubscribeHeaders: {});

Send

client.send(destination: '/foo/bar', body: 'Your message body', headers: {});

Disconnect

client.deactivate();

StompConfig

This table shows all available options in StompConfig

Option Description
url: String The url of the server you want connect to (required)
reconnectDelay: Duration Time duration between reconnect attempts. Set to 0 ms if you don't want to reconnect automatically. The default value is 5 seconds
heartbeatOutgoing: Duration Time duration between outgoing heartbeat messages. Set to 0 ms to not send any heartbeats. The default value is 5 seconds
heartbeatIncoming: Duration Time duration between incoming heartbeat messages. Set to 0 ms to not receive any heartbeats. The default value is 5 seconds
connectionTimeout: Duration Time duration it waits until a connection attempt is aborted. Set to 0 ms to not set a timeout. The default value is 0 ms
stompConnectHeaders: Map<String, String> Optional header values which will be used on the STOMP connect frame
webSocketConnectHeaders: Map<String, dynamic> Optional header values which will be used when connecting to the underlying WebSocket (not supported in Web)
beforeConnect: Future Function() An async function which will be awaited before a connection is established
onConnect: Function(StompFrame) Function to be called when the client successfully connects to the server
onDisconnect: Function(StompFrame) Function to be called when the client disconnects expectedly
onStompError: Function(StompFrame) Function to be called when the stomp server sends an error frame
onUnhandledFrame: Function(StompFrame) Function to be called when the server sends a unrecognized frame
onUnhandledMessage: Function(StompFrame) Function to be called when a subscription message does not have a handler
onUnhandledReceipt: Function(StompFrame) Function to be called when a receipt message does not have a registered watcher
onWebSocketError: Function(dynamic) Function to be called when the underyling WebSocket throws an error
onWebSocketDone: Function() Function to be called when the underyling WebSocket is done/disconnected
onDebugMessage: Function(String) Function to be called for debug messages generated by the internal message handler

Use Stomp with SockJS

Use StompConfig.sockJS constructor instead of default StompConfig constructor. Note: This library does not use SockJS as its underlying connection protocol. It uses normal WebSockets, but supports SockJS URLs and data packets (https://sockjs.github.io/sockjs-protocol/sockjs-protocol-0.3.3.html#section-36 & https://sockjs.github.io/sockjs-protocol/sockjs-protocol-0.3.3.html#section-42)

StompClient client = StompClient(
    config: StompConfig.sockJS(
        url: 'https://yourserver',
        onConnect: onConnectCallback
    )
);

Evaluation of headers

The STOMP client checks the content-type while parsing a received message. If the header contains the value application/octet-stream the message body will be treated as binary data. The resulting StompFrame will have a binaryBody. The body of the frame will be empty in this case. The same is true if the content-type header is missing.

Token Authentication (browser-based clients)

Browser clients can only use standard authentication headers (that is, basic HTTP authentication) or cookies and cannot provide custom headers (such as "Authorization" to use a Bearer token). Thus webSocketConnectHeaders will do nothing in a browser environment. Alternatives are:

  • Use the STOMP client to pass authentication headers at connect time. (recommended)
    • Use stompConnectHeaders to pass your headers in the CONNECT frame. Parse those headers on the server (i.e. by using a ChannelInterceptor in Spring)
  • Pass your authentication token/credentials as query parameter.

Development

Running unit tests

dart run test -p "chrome,vm" test/

Generating coverage data

dart pub global activate coverage
dart pub global run coverage:collect_coverage --port=8111 --out=coverage.json --wait-paused --resume-isolates & dart --disable-service-auth-codes --enable-vm-service=8111 --pause-isolates-on-exit test/test_all.dart

And to convert to lcov

dart pub global run coverage:format_coverage --lcov --in=coverage.json --out=lcov.info --packages=.packages --report-on=lib

stomp_dart_client's People

Contributors

abdulik avatar asashour avatar auguzsto avatar guillaume-fortin-keyneosoft avatar justacid avatar kammerertob avatar macdeveloper1 avatar mafanwei avatar peetee06 avatar robsonoduarte 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

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

stomp_dart_client's Issues

webSocketError callback is not fire in flutter web

StompClient client = StompClient(
config: StompConfig(
url: 'wss://yourserver',
onConnect: onConnectCallback,
onWebSocketError: onWebSocketError,

)

);

I am getting the connection error in the browser but not able to catch so not able to take any action on those cases please help me.

Note:- _channel = await platform.connect(config);
may be it will help to find the issue on above platform.connect() methos has some issue

Handling connection error stomp 0.3.3 flutter/dart

When I put a wrong url/ip to my stomp spring service I don't get onWebSocketError fired up and does not print out error as in the code below:

final stompClient = StompClient( config: StompConfig( url: 'ws://localhostxx:8080', onConnect: onConnect, onWebSocketError: (dynamic error) => print(error.toString()), stompConnectHeaders: {'Authorization': 'Bearer yourToken'}, webSocketConnectHeaders: {'Authorization': 'Bearer yourToken'}));

I want to show a message to user that the connection is not available. But when I correct my ip accessing the socket without any problem. What am I missing here?

Unable to connect SockJS when using https

I try to connect SockJS to an http connection and the connection is successful. Do not know if this is a bug or the library is not supported

stomp_dart_client: ^0.3.7

[✓] Flutter (Channel unknown, v1.17.5, on Mac OS X 10.15.7 19H2, locale vi-VN)
    • Flutter version 1.17.5 at /Users/tranduc/Documents/install/flutter
    • Framework revision 8af6b2f038 (4 months ago), 2020-06-30 12:53:55 -0700
    • Engine revision ee76268252
    • Dart version 2.8.4

[✓] Android toolchain - develop for Android devices (Android SDK version 29.0.3)
    • Android SDK at /Users/tranduc/Library/Android/sdk
    • Platform android-29, build-tools 29.0.3
    • Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6222593)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 12.1)
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Xcode 12.1, Build version 12A7403
    • CocoaPods version 1.10.0

[!] Android Studio (version 4.1)
    • Android Studio at /Applications/Android Studio.app/Contents
    ✗ Flutter plugin not installed; this adds Flutter specific functionality.
    ✗ Dart plugin not installed; this adds Dart specific functionality.
    • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6222593)

[✓] Connected device (1 available)
    • iPhone 12 Pro Max • 515E44A7-03EF-429D-B020-C8CA494B3815 • ios • com.apple.CoreSimulator.SimRuntime.iOS-14-1 (simulator)

! Doctor found issues in 1 category.
Process finished with exit code 0

🐛 [Web] `webSocketConnectHeaders` headers not sent to Server on `CONNECT`

I have the following StompClient initialization code.

StompClient _stompClient = StompClient(
        config: StompConfig.SockJS(
      url: socketUrl,
      webSocketConnectHeaders: {"Authorization": "Bearer <JWT Token here>"},
      onConnect: (frame) {
        print("Connected");
      },
      onWebSocketError: (error) =>
         print("Error");
    ));

I need to send an Authorization header in the CONNECT message in order to authenticate the request against backend.

When I use this code with Flutter Desktop for MacOS the headers are properly sent to the server and the request can be authenticated.

But using the same code and compiling the app for Flutter for Web, the header is not sent to the server.

Is this a package issue? Maybe I'm forgetting something different ?

singleton for the StompClient

In some cases, I do not want to close the connection when I leave the page. But when I enter this screen again. Always create new StompClient for me, and the previous StompClient is still connected.

I wandering that if it can be a singleton, and if it can support the subscribe management.

Trying to send base64 to stomp

hi I'm trying to send base64 String via stomp. but when i send base64 String data then connection is closed without any error in onStompError, onWebSocketError, onDisconnect, onUnhandledMessage . but when i send text message it works well
And onDebugMessage it sends me an message h
what is the meaning of 'h'???

here is my code

_stompClient = StompClient(
        config: StompConfig.SockJS(
            url: 'http://...',
            onConnect: _onConnect,
            beforeConnect: () async {
              print('Waiting to connect ...');
            },
            onWebSocketError: (dynamic error) {
              print('### WEBSOCKET THROW ERROR: ${error}');
              deactivateStomp();
            },
            onStompError: (StompFrame stompFrame) {
              print('### STOPM ERROR: ${stompFrame.body}');
              deactivateStomp();
            },
            onDisconnect: (StompFrame stompFrame){
              print('### DISCONNECT: ${stompFrame.body}');
            },
            onDebugMessage: (String message){
              print('### DEBUG: ${message}');
            },
            onUnhandledMessage: (StompFrame stompFrame){
              print('### UNHANDLE MESSAGE: ${stompFrame.body}');
            },
        )
    );

void _onConnect(StompFrame frame) {
    _stompClient.subscribe(
        destination: '/user/queue/human',
        callback: (frame) {
          HumanStomp result = HumanStomp.fromJson(json.decode(frame.body));
          _aiHumanController.add(result);
        }
    );
  }

void sendWav(String base64Audio) {
    _stompClient.send(
        destination: '/app/audioinput',
        body: json.encode({
          'audio': base64Audio
        })
    );
  }

StompBadStateException after call unsubscribe function.

Hello.

There are many sockets in our application and we ran into the problem when trying to call StompUnsubscribe:
StompBadStateException error occurs: The StompHandler has no active connection or the connection was unexpectedly closed.
This happens many times and we don't know how to fix it. It is important to note that this does NOT happen every time we try to call StompUnsubscribe.

Lib version: stomp_dart_client: ^0.4.2

flutter doctor

Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 2.0.5, on macOS 11.2.3 20D91 darwin-x64, locale en-GB)
[✓] Android toolchain - develop for Android devices (Android SDK version 29.0.3)
[✓] Xcode - develop for iOS and macOS
[✓] Chrome - develop for the web
[✓] Android Studio (version 4.0)
[✓] Connected device (2 available)

• No issues found!

How to pass clientID and username etc to authenticate and do durable subscriptions

How I can do durable subscriptions of topic and make the sending messages persistent

As Javascript has the following connect method to supply login and passcode etc.

client.connect(login, passcode, connectCallback);
  client.connect(login, passcode, connectCallback, errorCallback);
  client.connect(login, passcode, connectCallback, errorCallback, closeEventCallback);
  client.connect(login, passcode, connectCallback, errorCallback, closeEventCallback, host);

especially i want to know how the message broker will authenticate the client. and how to do durable subscriptions and message persistent.
https://activemq.apache.org/how-do-durable-queues-and-topics-work

WebSocketException: Connection to sevrer was not upgraded to websocket

Hello everyone, I'm asking for your help, I'm trying to connect my flutter application to my websocket server developed with spring and which uses apache's proxy. When I try with angular or postman it works.
This is my flutter code, I used a test server :

void onConnect(StompFrame frame) {
  stompClient.subscribe(
      destination: '/topic/test/subscription',
      callback: (frame) {
      List<dynamic> result = json.decode(frame.body);
      print(result);
    },
  );

  Timer.periodic(Duration(seconds: 10), (_) {
    stompClient.send(
      destination: '/app/test/endpoints',
      body: json.encode({'a': 123}),
    );
  });
}

final stompClient = StompClient(
  config: StompConfig(
    url: "wss://testwebsocketserver/secured",
    onConnect: onConnect,
    beforeConnect: () async {
      print('waiting to connect...');
      await Future.delayed(Duration(milliseconds: 200));
      print('connecting...');
    },
    onWebSocketError: (dynamic error) => print(error.toString()),
  ),
);
void main() {
  stompClient.activate();
}

I have this error WebSocketException: Connection to 'https://testwebsocketserver:0/secured/# was not upgraded to websocket I/flutter (23752): waiting to connect...

Reconnect issue

I am testing on local env.

I restart stomp server after connection and send message from subscribe channel.
but can't not receive message.

Please let me know correct code.

My code.

  ChatUtil._internal() {
    _client = StompClient(
        config: StompConfig(
            url: '${ChatRouter.websocketDomain}',
            reconnectDelay: 3000,
            heartbeatOutgoing: 5000,
            heartbeatIncoming: 5000,
            connectionTimeout: Duration(milliseconds: 5000),
            stompConnectHeaders: header,
            webSocketConnectHeaders: header,
            onConnect: onConnectCallback,
            onStompError: (StompFrame connectFrame) {
              print('onStompError');
            },
            onDisconnect: (StompFrame connectFrame) {
              print('onDisconnect');
            },
            onUnhandledFrame: (StompFrame connectFrame) {
              print('onUnhandledFrame');
            },
            onUnhandledMessage: (StompFrame connectFrame) {
              print('onUnhandledMessage');
            },
            onUnhandledReceipt: (StompFrame connectFrame) {
              print('onUnhandledReceipt');
            },
            onWebSocketError: (dynamic result) {
              print('onWebSocketError');
            },
            onWebSocketDone: () {
              if (unsubscribeFn != null) {
              }
            },
            onDebugMessage: (String msg) {
              print('$msg');
            }));

    _client.activate();
    // Disconnect
//    _client.deactivate();
  }

  void onConnectCallback(StompClient client, StompFrame connectFrame) {
    unsubscribeFn = _client.subscribe(
        destination: destination,
        headers: header,
        callback: (frame) {
          // Received a frame for this subscription
          print(frame.body);
        });
  }

WebSocketException: Connection to 'myUrl' was not upgraded to websocket

I'm trying to connect and I get this error.

WebSocketException: Connection to 'https://myUrl.com:0/updates/671/pngdcdac/websocket?viewedSubscriberId=123#' was not upgraded to websocket

I already tried changing the configuration constructor to SockJs and the url from wss:// to https:// as the comment mentions here
https://stackoverflow.com/questions/60847388/flutter-app-can-not-connect-to-a-websocket

And nothing has worked for me. Any idea?

Can't Connect to websocket

When I run your example using 'ws://echo.websocket.org' or my localhost. The websocket is not connecting. If I call stompClient.activate(); and check stompClient.connected it say false.
here is my code.

  final stompClient = StompClient(
        config: StompConfig(
            url: 'ws://echo.websocket.org',
            onConnect:(StompClient client, StompFrame frame) {

        },
           ));
    stompClient.activate();
    print("${stompClient.connected}");

Subscriptions persist after end of session

Hi again.

I'm using WebSocket to build a web app with live data.
When the app is destroyed, the WebSocket remains open.
I suppose I should call deactivate on the client when the app is destroyed.
There seems to be a conflict on this issue, calling a function when the app is destroyed.
I'm currently trying to call StompClient.deactivate() both with onDispose() and on didChangeAppLifecycleState.
neither works.

Is there something else I am supposed to use to clean up the WebSocket when the app is destroyed?

class _ApplicationState extends State<Application> with WidgetsBindingObserver{

  @override
  Widget build(BuildContext context) {

    return MaterialApp(...);
  }

  @override
    void didChangeAppLifecycleState(AppLifecycleState state) {
      print(state.toString());
      if(state == AppLifecycleState.inactive){
        log("INACTIVE");
        if(TempClientSingleton().client != null) TempClientSingleton().client.deactivate();
      }
      super.didChangeAppLifecycleState(state);
    }

  @mustCallSuper
  @protected
  @override
  void dispose() {
    if(TempClientSingleton().client != null) TempClientSingleton().client.deactivate();
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }
}
class TempClientSingleton{
  static final TempClientSingleton _singleton = TempClientSingleton._internal();
  factory TempClientSingleton(){return _singleton;}
  TempClientSingleton._internal();

  StompClient client;
}
class AuthenticationViewModel {
  static final AuthenticationViewModel _singleton = AuthenticationViewModel._internal();
  factory AuthenticationViewModel(){return _singleton;}
  AuthenticationViewModel._internal();

   ...

  void tryNewLogin() async {

      TempClientSingleton().client = WebSocketConnection().connect(
          email: loginEmail,
          password: loginPassword,
          userType: userType,
          onConnect: () async {},
          onError: (String error) {
            //Toast.show(error, context, duration: 3);
            MainViewModel().requestProgress.setData(RequestProgress.DENIED);
          }
      );
    }

  }
}
class WebSocketConnection{
  T cast<T>(x) => x is T ? x : null;

  StompClient connect({@required String email,@required String password,@required UserType userType,@required void Function() onConnect,@required void Function(String) onError}) {
    print('login attempt');
    if(email == null || password == null) return null;
    StompClient client = StompClient(
      config: StompConfig(
          url: 'ws://localhost:8080/schedule-application-websocket',
          reconnectDelay: Duration(milliseconds: 0),
          onConnect: (frame) => onConnect(),
          onWebSocketError: _onWebError,
          onStompError: _onStompError,
          webSocketConnectHeaders: {'email': email,'password':PasswordEncoder.encode(password), 'usertype' : EnumToString.convertToString(userType)},
          stompConnectHeaders: {'email': email,'password':PasswordEncoder.encode(password), 'usertype' : EnumToString.convertToString(userType)},
          connectionTimeout: Duration(seconds: 10)
      ),
    );

    client.activate();

    return client;
  }

  void _onStompError(StompFrame frame){
    //onError(frame.headers['message']);
  }

  void _onWebError(dynamic error){
    //onError(error);
  }
}

supporting ws protocol

Hello,
I'm trying to use this package with my project, and out web socket using ws protocol but unfortunatly
I was not able to connect.
I think support ws protocol it will be a big benefit.
thanks

how can i get response cookie from this package ?

hi

how can i get response cookie from this package ?

with this package, I also use http package to call spring boot controller.

i'm trying to integrate socket and http to use the same session id.

to make, i ask you how to get response cookie for session id(actually redis spring session id) through your package in order to set the session id to the hedder of http.

thank you in advance.

i can't connect over from http

Hello. i want to connect websocket over from http url. But it gives error:

WebSocketException: Unsupported URL scheme 'http'

what can i do?
Thanks.

the callback in Stomp.dart is always null

Hello,
After installing the package I notice that the callback in stomp.dart is marked in red and showing an error
Capture du 2020-04-02 08-07-34.
And after testing my callback function is always null
Capture du 2020-04-02 08-33-07
And here is the part of the code where I am using the stomp
Capture du 2020-04-02 08-08-08

HeartBeat Duration

I am trying to use this library to connect to websocket servers. During the handshaking client sending this connect CONNECT
accept-version:1.0,1.1,1.2
heart-beat:0:00:00.000000,0:00:00.000000

(this is message for heartbeat set to empty Duration(), for any time value it does not work too)
the format of hear beat is wrong and every WS with stomp returns ERROR message.

If I edit the lib and put in connectHeaders plain numbers it just work. Probably issue with Duration output format..

unable to subscribe

successfully connected to the server, but unable to subscribe to the topic as it looping into the timer function and continuously printing 'i am in timer' added my sort of code please guide me the right path

void onConnect(StompFrame frame) {
  stompClient.subscribe(
    destination: '/topic/messages/chatName',
    callback: (frame) {
      print('I am in callback of the subscription');
      List<dynamic>? result = json.decode(frame.body!);
      print(result);
    },
  );
  Timer.periodic(Duration(seconds: 5), (_) {
    print('i am in timer');
    stompClient.send(
      destination: '/app/chat.send',
      body: json.encode({'aa': "123",}),
    );
  });
}

Bug correction on SockJS haven't solved the problem

Hello.

I'm trying to connect to my websocket server like this

stompClient = StompClient(
            config: StompConfig.SockJS(
                url: 'https://<server>/ws-connect',
                onConnect: onConnect,
                onStompError: (frame) => print("ERROR")
            )
        );

        stompClient.activate();

My Spring server is configured like this:

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

	@Override
	public void configureMessageBroker(MessageBrokerRegistry config) {
		config.enableSimpleBroker("/ws-events");
		config.setApplicationDestinationPrefixes("/app");
	}

	@Override
	public void registerStompEndpoints(StompEndpointRegistry registry) {
		registry.addEndpoint("/ws-connect")
		.setAllowedOrigins("*")
		.withSockJS();
	}
}

But the onconnect function never runs, neither does the onStompError.
I saw that there was a bug that was already corrected on #33, but I already changed my pubspec.yaml file like this

stomp_dart_client:
    git:
      https://github.com/blackhorse-one/stomp_dart_client.git

I know the server is working correctly, because I created a small javascript client like this:

var socket = new SockJS('https://<server>/ws-connect');

    stompClient = Stomp.over(socket);
    stompClient.connect({}, function (frame) {
        setConnected(true);
        console.log('Connected: ' + frame);
        stompClient.subscribe('/ws-events/' + $("#roomID").val(), function (greeting) {
            showGreeting(JSON.parse(greeting.body).content);
        });
    });

and this works perfectly.
Am I doing anything wrong?

Could not connect websocket on SpringBoot + SockJS

I am implementing you package in web app. The code example below. When I click on button I expect an least the connection to remote server (Networks tab of browser inspector) but I didn't see it. Object is created. Then I call activate method bu onConnect callback does not call. Does the pacakge really works under web environment? What I did wrong? Please help.

onPressed: () {
  final client = StompClient(
    config: StompConfig.SockJs(
      url: 'http://10.1.1.1:8080/topics',
      onConnect: (cli, frm) {
        print('connected'); // never printed
      }
    ),
  );
  client.activate();
}

Incoming message with binary body

Hi and thanks for the very nice package.

I'm using the package to connect to a Spring server. The server sends messages which have a binary body. I realised that the stomp_dart_client doesn't handle them (leads to an error and no StompFrame). I opened a PR with a possible solution to the problem. Could you have a look and let me know what you think about it?

Here is a description of the PR content (same as in the PR message):

If an incoming message has a certain content type the body should be treated as binary. The configuration allows to add content types as a list of Strings. A value of 'null' would treat missing 'content-type' headers as having a binary body (this behaviour is mentioned in the STOMP 1.1 spec).

The first commit changes the StompParser to treat a message with content type 'application/octet-stream' as having a binary body. This is needed when e.g. working with Spring (here a binaryBody leads to a 'content-type:application/octet-stream').

The second commit adds the possibility to configure content types which should lead to a StompFrame with a binary body. This allows more flexibility for different use cases.

Is cookie-based authentication supported?

Hi everyone, if you don't mind, would you please answer few questions?

  1. How to include Cookie header in Request when the HTTP handshake happens?

TL:DR

Hi everyone, I am thinking to be a Flutter developer after realizing React Native has issue with Set-Cookie. I did a quick search and found out about the Flutter's cookie class, I am assuming Flutter does not have issue with Set-Cookie.

The current architecture I use, I build a Single Page Application for Browser to demonstrate the features, the repository, a simple Chat App:

  1. Hit a http://localhost:8080/login endpoint, get Set-Cookie header, the browser will store JSESSION and XSRF-TOKEN as cookies.
  2. Do CONNECT ws://localhost:8080/chat, the HTTP handshake will contain Cookie header with JSESSION and XSRF-TOKEN. The CONNECT frame would need X-XSRF-TOKEN header.
  3. Do SUBSCRIBE /topic/channel/1, /user/queue/messages
  4. Do MESSAGE /app/channel/1 to send channel message and Do MESSAGE to send private message.

onDebugMessage doesn't work

Can you add onDebugMessage inside StompConfig copyWith method?

when StompConfig copyWith method is called, the onDebugMessage calback is copied with _noOp default value.

So i can't debug websocket messages.

could we wrap the ping payload with a bracket ?

for ping payload, we just send a newline ("\n") as its payload, however for common message sent, the payload is wrapped with a bracket like this:

["SEND\ndestination:/app/webMessage\ncontent-length:236\n\n{"\"content\":\"Greeting from client (2020-08-25 15:45:26.904648)\",\"senderType\":0,\"type\":\"text\",\"flag\":\"1598341526907\",\"createTime\":\"2020-08-25 15:45:26.907645\"}\u0000"]

could we keep them be consistent? currently if my client sent a newline as a ping payload, my backend would immediately close my connection with code=1007.

if I wrap the newline with bracket like this: '[\n]' for ping payload, it works.

Send message to a specific user

Hello,

I would like know, if with this package, I can send messages to a specific user.

In JavaScript I've see something like this :

function connect() {
var socket = new SockJS('/connect');
stompClient = Stomp.over(socket);
stompClient.connect({
"user" : user Here
} , function (frame) {
stompClient.subscribe('/user/queue/reply', function(greeting) { greeting.body });
});
}

work with streams

thank you for the quality of your library, I would like to know if there is interest in implementing the subscribe so that it returns a Stream instead of using callbacks.
I would be willing to raise a PR for this.

Disconnects while sending long base64 message

Sending a message with base64 disconnects the connection and re-connect.

jsonObj = {
          'chatId': 'ProjectC',
          'senderId': '[email protected]',
          'receiverId': 'ProjectC',
          'tfProjectNumber': 'ProjectC',
          'chatName': 'ProjectC',
          'fileName': 'IMG-20210704-WA1002.jpg',
          'textMessage': '',
          'typeOfMessage': 'media',
          'messageText': '',
          'projectFile':
              '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'
        }

stompClient.send(
        destination: '/app/chat/send',
        body: json.encode(jsonObj),
      );

Logs -

waiting to connect...
connecting...
connected
sending message...
waiting to connect...
connecting...
connected

Tried with the latest version i.e. 0.4.0
Please help!

Uncaught Exception on .activate()

Hi, I'm first using your lib and I get from debugger an Uncaught Exception when I execute .activate() function and the WebSocket Server is down, even surround between try catch block. Even I was defined a function for onWebSocketError but is not executed, i think that because the exception throw before websocket connect.

image
image
image

I think that the problem is that .activate() call an async function that execute outside try catch block...???

When switching from wi-fi to mobile internet, the StompClient stops accepting data from the websocket without any messages.

If you have wi-fi and mobile internet turned on and then you turned off wi-fi, StompClient without any messages via onDebugMessage, onWebSocketError, onStompError, etc. will stop receiving messages from WebSocket. Can you check this issue?

You may notice a similar behavior if, during the exchange of messages using the StompClient, a call is received on the device. Usually, 20 seconds on the phone is enough for the StompClient not to recover.

Connection issue callbacks not working on emulator

Hello,

First I want to say that this is really a nice package. It's really easy to use.

In my application I am trying to handle connection loss or internet issues. I subscribed on all the possible callbacks:
image
The setup of the connection is working. I get messages from the server. I know this because it's shown in the log. The problem is when I disable the internet connection on the emulator none of those callbacks are called. Yet when I try this on a real device it works perfectly fine, multiple callbacks are called. I have tried it on several emulators but none worked. Can you help me resolve this problem?

Flutter web fails to connect using SockJs

Hello,

I'm trying to use the SockJs implementation with flutter for iOS, Android and Web.

It works great with Android and iOS but it fails to connect with flutter web with the following error in the last catch of StompHandler.connect() :

DomException (SyntaxError: Failed to construct 'WebSocket': The URL contains a fragment identifier (''). Fragment identifiers are not allowed in WebSocket URLs.)

It seems that the problem is SockJsUtils.generateTransportUrl(String url) which generates transportUrl with a trailing '#'

I've written a simple test that shows the problem:

test('generate websocket url with no empty fragment', () {
      final url = 'https://localhost:5000/test';

      final webSocketUrl = SockJsUtils().generateTransportUrl(url);

      expect(webSocketUrl, isNotNull);
      expect(webSocketUrl, isNotEmpty);
      expect(webSocketUrl.endsWith('#'), isFalse);
    });

reconnect delay doesn't work when i disconnect network card

If reconnect delay is not equals to 0, the stomp_handler must launch new connect.
this functionnality work when a TimeoutException is occured but not when a WebSocketChannelException is occured.

In my case, i disconnect my network adapter, a WebSocketChannelException occure, and the stomp handler doesn't start a new connect after reconnect delay.

Can you change the code from StompHandler start method like that:

on WebSocketChannelException catch (err) {      
      if (config.reconnectDelay == 0) {
        _onError(err);
      } else {
        config.onDebugMessage('Connection web socket error...reconnecting');
        _onDone();
      }
    }

full code

void start() async {
    try {
      channel = await platform.connect(config);
      channel.stream.listen(_onData, onError: _onError, onDone: _onDone);
      _connectToStomp();
    } on WebSocketChannelException catch (err) {      
      if (config.reconnectDelay == 0) {
        _onError(err);
      } else {
        config.onDebugMessage('Connection web socket error...reconnecting');
        _onDone();
      }
    } on TimeoutException catch (err) {
      if (config.reconnectDelay == 0) {
        _onError(err);
      } else {
        config.onDebugMessage('Connection timed out...reconnecting');
        _onDone();
      }
    } catch (err) {
      _onDone();
    }
  }

Sorry, i can't pull request because i am currently adding capability to connect to STOMP with SOCKJS.
I will pull request SOCKJS parser in few days.
you can watch the changes here

can not be used in todo app in real time

hi ,
i am building a todo app for like chat system ... in which multiple users can add/update todoList .

i am able to get broadcast msg when any user add ... but can not update todoList.

i did not understand how i pass todoList variable or any other variable in callback .

Problems connecting from browser - working in test environment

class MVCWS{
  String value1 = 'value';
  String value2 = 'value';
  StompClient client;
  Future<bool> login() async {
    Completer<bool> completer = new Completer();
    client = StompClient(
      config: StompConfig(
          url: 'ws://localhost:8080/schedule-application-websocket',
          onConnect: (StompFrame frame){
            print('connected');
            completer.complete(true);
          },
          beforeConnect: () async {
            print('connecting...');
          },
          onWebSocketError: (dynamic error) => (){
            client = null;
            completer.complete(false);
          },
          webSocketConnectHeaders: {'value1': value1,'value2':PasswordEncoder.encode(value2)},
          connectionTimeout: Duration(seconds: 10)
      ),
    );
    client.activate();

    return completer.future;
  }
}

I have set up a spring WebSocket server to handle this request.
The above code works when using it in a test.
When I use it in a browser application, the server receives no connection.
I have placed print methods in the servers handshake method, and they don't show for the application.

Looking past this hurdle the rest of the package is great and exactly what I need for my project, thank you for that!

StompClient's `connected` returns false after connection

I have my own wrapper for your StompClient

class StompSocket {
  StompSocket() {
    _stompClient = StompClient(
      onConnect: _onConnect
      // all other props
    );
  }
  StompClient _stompClient;

  void connect() {
    if (!_stompClient.connected) {
      _stompClient.activate();
    }
  }

  void listen() {
    if (_stompClient.connected) {
      _stompClient.send(...);
      print('start listening');
    }
  }

  void _onConnect(StompClient client, StompFrame frame) {
    client.subscribe(
      destination: '/topic/some',
      callback: (StompFrame frame) {
        // some statements
      });
    print('connected');
  }
}

void main() {
  final stompClient = StompClient()..connect();
  stompClient.listen();
}

I see only one printed line connected but don't see start listening. So the question why _stompClient.connected is false if virtually is is connected?

Cannot sent HTTP headers using flutter web

I have a problem with sending authorization header because I need secure connection to websocket. I see two kinds of connect method under dart wm and web:

_connect_io.dart

Future<WebSocketChannel> connect(StompConfig config) async {
  var websocket = WebSocket.connect(config.url. headers: config.webSocketConnectHeaders); // headers are passed
  ...
}

_connect_html.dart

Future<WebSocketChannel> connect(StompConfig config) async {
  var websocket = WebSocket(config.url)..binaryType = BinaryType.list.value; // where the headers?
  ...
}

In html implementation there is no possibility to send authorization headers (or even any headers). Is it possible to fix this? Do you know some workaround for a while?

How to implement an exponential reconnect backoff delay

I need increment the reconnection delay time exponentially lets say first try to connect 3000 ms, second try 6000 ms and so on.

But it seems not be possible with the current config parameter reconnectDelay which seems to be final value and therefore its value cannot be updated once the config is created.

Is there any way to implement that behavior or am I missing something here?

Need help! Springboot websocket + flutter - not receiving messages from backend

Hello, i'm use flutter as frontend and springboot as backend.
For now my app can connect to backend via websocket, can subscribe for channel, can send message from frontend to backend,
but flutter app didn't recieve any messages from backend and i get no errors.

Flutter part:

  Map<String, String> _headers=Map();
  _headers.addAll({'authorization':'${globals.tokenType} ${globals.token}'});
  
  if (_socketServer.isNotEmpty) {
    //_socketServer=_socketServer.replaceFirst(new RegExp(r'https://'), 'wss://');
    _socketServer+='ws';
  }
  
  client = StompClient(
      config: StompConfig.SockJS(
          url: _socketServer,
          //connectionTimeout: Duration(seconds: 8),
          onConnect: _socketOnConnect,
          stompConnectHeaders: _headers,
          onDebugMessage: (frame) {
            print('WebSocket - onDebugMessage executed! - $frame');
          },
          onStompError: (StompFrame frame) {
              print('A stomp error occurred in web socket connection :: ${frame.toString()}');
          },
          onWebSocketError: (dynamic frame) {
              print('A Web socket error occurred in web socket connection :: ${frame.toString()}');
          },
          onWebSocketDone: () {
              print('WebSocket - onWebSocketDone executed!');
          },
          onDisconnect: (frame) {
            print('WebSocket - onDisconnect executed! ${frame.toString()}');
          },
          onUnhandledFrame: (frame) {
            print('WebSocket - onUnhandledFrame executed! ${frame.toString()}');
          },
          onUnhandledMessage: (frame) {
            print('WebSocket - onUnhandledMessage executed! ${frame.toString()}');
          },
          onUnhandledReceipt: (frame) {
            print('WebSocket - onUnhandledReceipt executed! ${frame.toString()}');
          },
      )
  );
  print('try to connect wss to: $_socketServer');
    try {
      client.activate();
      print('[1] Socket client activated.');
      if (mounted) {
        setState(() {
          _isSocketConnected=true;
        });
      }
    } catch(e) {
      print('!!!===Socket connection error: $e');
      client?.deactivate();
    }
  }

  _socketOnConnect(StompFrame data) {
    print('On connect: ${client.config.url.toString()} | ${data.body} | ${data.headers}');
    unsub=client.subscribe(destination: '/websocket/app',
          callback: _socketIncomingMessage
    );
    client.send(destination: '/websocket/send', body: 'SOCKET TEST');
  }
  
  _socketIncomingMessage(StompFrame data) {
    print('Received message from subscribed channel: ${data.toString()}');
  }

  _disconnectSocket() {
    try {
      unsub();
      client?.deactivate();
      print('[2] Socket client is deactivated.');
      unsub=null;
      if (mounted) {
        setState(() {
          _isSocketConnected=false;
        });
      }
    }
    catch(e) {
      print('!!!===Socket disconnection error: $e');
      unsub=null;
    }
  }

flutter debug output:

I/flutter ( 3826): WebSocket - onDebugMessage executed! - >>> ["CONNECT\nauthorization:Bearer ************************************************* \naccept-version:1.0,1.1,1.2\nheart-beat:5000,5000\n\n\u0000"]
I/flutter ( 3826): WebSocket - onDebugMessage executed! - <<< o
I/flutter ( 3826): WebSocket - onDebugMessage executed! - <<< a["CONNECTED\nversion:1.2\nheart-beat:0,0\nuser-name:************************* \n\n\u0000"]
I/flutter ( 3826): On connect: wss://...:8443/websocket/082/ppqcum2j/websocket | | {version: 1.2, heart-beat: 0,0, user-name: *****************}
I/flutter ( 3826): WebSocket - onDebugMessage executed! - >>> ["SUBSCRIBE\ndestination:/websocket/app\nid:sub-0\n\n\u0000"]
I/flutter ( 3826): WebSocket - onDebugMessage executed! - >>> ["SEND\ndestination:/websocket/send\ncontent-length:11\n\nSOCKET TEST\u0000"]
I/flutter ( 3826): WebSocket - onDebugMessage executed! - <<< h
I/flutter ( 3826): WebSocket - onDebugMessage executed! - <<< h
I/flutter ( 3826): WebSocket - onDebugMessage executed! - <<< h
I/flutter ( 3826): WebSocket - onDebugMessage executed! - <<< h
I/flutter ( 3826): WebSocket - onDebugMessage executed! - <<< h
I/flutter ( 3826): WebSocket - onDebugMessage executed! - <<< h

Spring boot message controller and debug output:

@RestController
public class MessageController {
    @Autowired
    private SimpMessageSendingOperations messagingTemplate;

    @MessageMapping("/send")
    public void onReceiveMessage(@Payload String chatMessage, @CurrentUser UserPrincipal userPrincipal) {
        System.out.println("Received message from client - [userName:"+userPrincipal.getUsername()+
            "] - [userEmail:"+userPrincipal.getEmail()+"] - [userId:"+userPrincipal.getId()+"] - message: "+
            chatMessage);
        messagingTemplate.convertAndSend("/websocket/app", chatMessage);
    }
}

14:27:10.941 [https-openssl-nio-8443-exec-8] DEBUG o.s.web.filter.CommonsRequestLoggingFilter - Before request [GET /websocket/082/ppqcum2j/websocket, client=...** , headers=[user-agent:"Dart/2.12 (dart:io)", connection:"Upgrade", cache-control:"no-cache", accept-encoding:"gzip", content-length:"0", sec-websocket-version:"13", host:"...:8443", sec-websocket-extensions:"permessage-deflate; client_max_window_bits", sec-websocket-key:"*************", upgrade:"websocket"]]

Stomp command CONNECT received!

Received a new websocket connection. - SessionConnectedEvent[GenericMessage [payload=byte[0], headers={simpMessageType=CONNECT_ACK, simpConnectMessage=GenericMessage [payload=byte[0], headers={simpMessageType=CONNECT, stompCommand=CONNECT, nativeHeaders={authorization=[Bearer ************************************************************************************************************************], accept-version=[1.0,1.1,1.2], heart-beat=[5000,5000]}, simpSessionAttributes={}, simpHeartbeat=[J@476bdeff, simpUser=org.springframework.security.authentication.UsernamePasswordAuthenticationToken@db12a52: Principal: *******************************.security.UserPrincipal@49ac2862; Credentials: [PROTECTED]; Authenticated: true; Details: null; Granted Authorities: ROLE_USER, simpSessionId=ppqcum2j}], Stomp command SUBSCRIBE received!

Subscribe user is: ********************

Received a subscribe event from websocket. - SessionSubscribeEvent[GenericMessage [payload=byte[0], headers={simpMessageType=SUBSCRIBE, stompCommand=SUBSCRIBE, nativeHeaders={destination=[/websocket/app], id=[sub-0]}, simpSessionAttributes={}, simpHeartbeat=[J@4219c8bd, simpSubscriptionId=sub-0, simpUser=org.springframework.security.authentication.UsernamePasswordAuthenticationToken@db12a52: Principal: *********************************.security.UserPrincipal@49ac2862; Credentials: [PROTECTED]; Authenticated: true; Details: null; Granted Authorities: ROLE_USER, simpSessionId=ppqcum2j, simpDestination=/websocket/app}]]

Reply channel is: /websocket/app | id=sub-0

Received message from client - [userName:********************* ] - [ userId: ************************* ] - message: SOCKET TEST

After successful connection of StompConfig, the disconnect instruction is sent.

When I use this package to connect to websocket successfully, the callback of successful connection is not triggered. However, the server receives a connection instruction and a disconnection instruction. My code is like this

final StompClient client = StompClient(
      config: StompConfig(
          url: 'ws://192.168.0.120:8091/',
          reconnectDelay: 0,
          onConnect: (StompClient client, StompFrame connectFrame) {
            print("onConnect");
          },
          stompConnectHeaders: {},
          onWebSocketError: (dynamic error) => print(error.toString()),
          onDisconnect: (StompFrame frame) => { print("onDisconnect") },
          onStompError: (StompFrame frame) => { print("onStompError") },
          onDebugMessage: (String frame) => { print(frame) },
          onUnhandledFrame: (StompFrame frame) => { print("onUnhandledFrame") },
          onUnhandledMessage: (StompFrame frame) => { print("onUnhandledMessage") },
          onWebSocketDone: () => {print("onWebSocketDone")}
          ));

void main() {
  client.activate();
}

can't use send function

Hello,
I'm trying to use WSS with my app and it's connected successfully.
but when i trying to send the server not receiving my message, also send function not giving any errors
StompClient _client = StompClient( config: StompConfig( onConnect: onConnectCallback, connectionTimeout: Duration(minutes: 10), url: 'wss://URL/wathiq-ws', onDisconnect: (StompFrame frame) { print(frame); }, onWebSocketDone: () {}, onWebSocketError: (dynamic err) { print(err); }, onStompError: (StompFrame frame) { print(frame.toString()); }));

client.send( destination: '/app/message/123', body: '{"text":"wss test","counter":"$counter"}');

is there any suggestion to track this bug? and thanks in advance

how to call void method?

class MyHomePage extends StatefulWidget {

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  // List<String> messageList = [];
  List<dynamic> list = [];

  StompClient client = StompClient(
      config: StompConfig(
          url: 'ws://##.##.##.###:####/##',
          onConnect: (StompClient client, StompFrame connectFrame) {
            client.subscribe(
                destination: '/###/###,
                callback: (frame) {
                  setState(() {
                    list.add(frame.body);
                  });
                });
          }));

  @override
  void initState() {
    super.initState();
    client.activate();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          actions: <Widget>[],
        ),
        body: Column(
          children: <Widget>[
            Expanded(
                child: ListView.builder(
              itemCount: list.length,
              itemBuilder: (context, index) {
                return ListTile(
                  title: Container(
                    child: Padding(
                      padding: const EdgeInsets.all(8.0),
                      child: Text(
                        list[index],
                        style: TextStyle(fontSize: 22),
                      ),
                    ),
                    color: Colors.teal,
                    height: 60,
                  ),
                );
              },
            )),
          ],
        ));
  }
}

Unable to send Uint8List (version 3.8)

Unable to send ByteArray in binary body property.

Version: 3.8

Cause: _TypeError (type 'Uint8List' is not a subtype of type 'String')

Where: sock_js_parser.dart

When: sending StompFrame with Uint8List property non null.

  @override
  dynamic serializeFrame(StompFrame frame) {
    dynamic serializedFrame = _stompParser.serializeFrame(frame);

    serializedFrame = _encapsulateFrame(serializedFrame);

    return serializedFrame;
  }

Use Case: Selecting image with FilePicker package and dart.io then sending it as byte array (non encoded for performance reasons)

  void sendImage() async {
    var file = await selectImage();
    Uint8List bytes = await file.readAsBytes();
    if (file != null) {
      stompClient.send(
        destination: '/app/imageQueue',
        body: "demo",
        binaryBody: bytes,
      );
    }
  }

  Future<File> selectImage() async {
    var result = await FilePicker.platform.pickFiles();

    if (result != null) {
      var file = File(result.files.single.path);
      return file;
    } else {
      return null;
    }
  }

Error when I try connect to server in Flutter 1.20.

Hi, this error give me when I try connect to the server. How to fix it?

[VERBOSE-2:ui_dart_state.cc(166)] Unhandled Exception: type '_Uint8ArrayView' is not a subtype of type 'String'
#0      StompHandler._onData (package:stomp_dart_client/stomp_handler.dart:170:36)
#1      _rootRunUnary (dart:async/zone.dart:1198:47)
#2      _CustomZone.runUnary (dart:async/zone.dart:1100:19)
#3      _CustomZone.runUnaryGuarded (dart:async/zone.dart:1005:7)
#4      _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:357:11)
#5      _BufferingStreamSubscription._add (dart:async/stream_impl.dart:285:7)
#6      _ForwardingStreamSubscription._add (dart:async/stream_pipe.dart:127:11)
#7      _HandleErrorStream._handleData (dart:async/stream_pipe.dart:266:10)
#8      _ForwardingStreamSubscription._handleData (dart:async/stream_pipe.dart:157:13)
#9      _rootRunUnary (dart:async/zone.dart:1198:47)
#10     _CustomZone.runUnary (dart:async/zone.dart:1100:19)
#11     _CustomZone.runUnaryGuarded (dart:async/zone.dart:1005:7)
#12     _BufferingStreamSubscription._sendData (dart:asyn<…>

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.