konsultaner / connectanum-dart

This is a WAMP client (Web Application Messaging Protocol) implementation for the dart language and flutter projects.
MIT License
21 stars 14 forks source link

Auth failure response doesn't seem right #55

Closed malibu1966 closed 12 months ago

malibu1966 commented 1 year ago

Hi there, I am using Connectanum 2.0.3 on Android Studio "Electric Eel" with the Android Emulator, Pixel 6 Pro API 33. I am using regular Ticket authentication and connecting to my crossbar.io server 22.6.1. The server itself works fine, I have it working with a Qt 5 application and an iOS Swift app. Authentication on the server is dynamic authentication that raises ApplicationError if the auth does not succeed. I have followed the examples provided for a basic connection, which is to create the client and then await the client.connect().first and assign to session.

I am having trouble understanding how to detect a credential failure or a connectivity issue. When I purposefully enter a bad 'ticket' with the example that contains 10 retries and a delay of 200 milliseconds I get the following errors streaming forever:

[ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: Bad state: StreamSink is closed
                                                                                                    #0      _StreamSinkImpl.add (dart:_http/http_impl.dart:902:7)
                                                                                                    #1      _WebSocketImpl.addUtf8Text (dart:_http/websocket_impl.dart:1228:11)
                                                                                                    #2      WebSocketTransport.send (package:connectanum/src/transport/websocket/websocket_transport_io.dart:103:16)
                                                                                                    #3      Session.authenticate (package:connectanum/src/protocol/session.dart:218:16)
                                                                                                    #4      Session.start.<anonymous closure>.<anonymous closure> (package:connectanum/src/protocol/session.dart:134:53)
                                                                                                    <asynchronous suspension>
                                                                                                    #5      Session.start.<anonymous closure>.<anonymous closure> (package:connectanum/src/protocol/session.dart:134:27)
                                                                                                    <asynchronous suspension>

[ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: Bad state: Future already completed
                                                                                                    #0      _AsyncCompleter.complete (dart:async/future_impl.dart:35:31)
                                                                                                    #1      WebSocketTransport.receive.<anonymous closure> (package:connectanum/src/transport/websocket/websocket_transport_io.dart:117:28)
                                                                                                    <asynchronous suspension>
                                                                                                    #2      WebSocketTransport.receive.<anonymous closure> (package:connectanum/src/transport/websocket/websocket_transport_io.dart:113:24)
                                                                                                    <asynchronous suspension>

In fact, I get these errors with any number of retries, even 0.

If I try without any connection options, I get this:


[ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: Instance of 'Abort'
2023-03-15 15:26:19.675 24694-24727 flutter                 biz.unwait.tactical_flutter          E  [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: Null check operator used on a null value
                                                                                                    #0      Client._connect.<anonymous closure> (package:connectanum/src/client.dart:87:51)
                                                                                                    <asynchronous suspension>

Any ideas? Thanks.

konsultaner commented 1 year ago

Hey @malibu1966

The example assumes that you know how Streams in dart work. I should change that example. If you use .first, you will only get the first element and the await succeeds. All following elements will be ignored. You should read the Stream-Section of the Dart docs.

You could use either a for loop or the listen() method to read the stream and all errors coming from that stream.

You could also check the libraries test cases to see how it works.

Sorry for the trouble 🙈

malibu1966 commented 1 year ago

Thanks for the fast response! I am somewhat familiar with streams. I've been doing them in python for some time and I'm trying to make this app all stream based. Admittedly, even after doing a few rxdart tuts and videos I am struggling with them a bit still but I really want to learn.

Assuming the .first was supposed to be there is probably my issue then. Thank you I will focus on your recommendations.

konsultaner commented 1 year ago

@malibu1966 here is a good example from the test

https://github.com/konsultaner/connectanum-dart/blob/a9ecb58f51d3a09530e446ac7c4f2ae47106a562/test/client_test.dart#L98-L100

konsultaner commented 1 year ago

please close if you find this issue is solved for you!

malibu1966 commented 1 year ago

I did this already but I'm still getting the errors?

            Completer abortCompleter = Completer<Abort>();
            var subscription = client!
                .connect(
                    options: ClientConnectOptions(
                     reconnectCount: 0, // Default is 3
                     reconnectTime: const Duration(
                         milliseconds: 200) // default is null, so immediately
                     // you may add ping pong options here as well
                ))
                .listen((data) {session = data;}, onError: ((abort) => abortCompleter.complete(abort)));
            Abort abort = await abortCompleter.future;
            print("ABORT RECEIVED");

It never gets to the print statement. I tried it with different reconnectCount values and with .listen( (_) {}, onError: as it is in the test as well. If I take out the options and just use .connect() I still get that null error.

konsultaner commented 1 year ago

do you have your project on github? I would check it out and give it a try. I know that the guys from crossbar are actually using this lib for an internal project. they also contributed. It should work. maybe the ticket authenticator has a problem. have you tried wamp-cra?

konsultaner commented 1 year ago

did you set a reconnectTime ?

malibu1966 commented 1 year ago

Yes the reconnectTime is set to 200 ms as per the demo.

I will set up a static CRA and try it. Perhaps I will try the crossbar forum and see if anyone can assist there.

konsultaner commented 1 year ago

I will digg into it.

konsultaner commented 1 year ago

@KSDaemon have you experienced similar issues with your implementation?

malibu1966 commented 1 year ago

I'm not absolutely sure what you mean by that. I used MDWamp for my native iOS app and it seems to work fine in this circumstance; it calls onDisconnect and I report the error.

In python I have a problem where it won't stop reconnecting but apparently that is for a different reason, I have to stop the app component rather than stopping the session. Also that is different in that it doesn't go into an internal loop. It does the reconnect cycle until the correct ID and password are entered. Also, neither ios or python versions are stream based. Did that answer your question?

malibu1966 commented 1 year ago

I tried a basic static ticket auth and it does the same thing. I tried CraAuthentication and the server gives "WAMP-CRA client signature is invalid (expected X but got Y)" even for correct credentials.

I'm using crossbar server 22.6.1, I think tomorrow I will try upgrading to 22.7.1

KSDaemon commented 1 year ago

Hm... Really strange. We do not have such problems. We are doing all types of auth without problems.

malibu1966 commented 1 year ago

What version of crossbar are you using? I can't upgrade to 22.7.1 because it's not in Pypi.

Can you provide me with an example of code that handles errors?
Is it possible something has changed in the dependencies?

Here is my entire client class. I know it's not complete, but do you see a reason why it wouldn't connect with a static CRA method if the credentials are correct? I do get to the ABORT RECEIVED but then after that I get the looping exceptions again.

I really appreciate the help on this.

import 'package:connectanum/connectanum.dart';
import 'package:connectanum/authentication.dart';
import 'package:connectanum/json.dart';
import 'dart:async';
import 'package:tactical_flutter/data/abstract_wamp_client.dart';
class ParamException implements Exception {
    String errMsg() => 'BasicWampClient should have username and password';
}
class ConnectionException implements Exception {
    String errMsg() => 'Not able to connect';
}

class BasicWampClient extends AbstractWampClient {
    StreamSubscription<Session>? client_subscription;
    BasicWampClient();

    @override
    void connect({String? username,String? uid, String? password}) async {
        if (username != null && password != null && uid == null) {
            var transport = WebSocketTransport(
                "ws://6.6.6.2:9000/test",
                Serializer(),
                WebSocketSerialization.serializationJson
            );
            client = Client(
                authId: username,
                authenticationMethods: [CraAuthentication(password)],
                realm: "myrealm",
                transport: transport
            );
            Completer abortCompleter = Completer<Abort>();
            client_subscription = client!
                .connect(options: ClientConnectOptions(
                reconnectCount: 1, // Default is 3
                reconnectTime: const Duration(
                    milliseconds: 200)))
                .listen((data){
                    session = data;
                    }, onError: ((abort) => abortCompleter.complete(abort)));
            Abort abort = await abortCompleter.future;
            print("ABORT RECEIVED");
        }
        else {
            throw ParamException();
        }

    }
}
malibu1966 commented 1 year ago

So, I found this comment in websocket_transport_io.dart:

/// This transport type is used to connect via web sockets /// in a dart vm environment. A known issue is that this /// transport does not support auto reconnnect in the dart vm /// use [SocketTransport] instead! This may not work if your /// router does not raw socket transport.

I could have sworn that I had tried disabling reconnectCount before, but either I did not do it right or because I hadn't completed onReady/onDisconnect/onConnectionLost I wasn't interpeting results correctly.

Anyway, I guess if you need to use WebSocketTransport you can't have autoconnecting and the reconnectCount value must be 0?

konsultaner commented 1 year ago

@malibu1966 thats embarrassing 😖 I totally forgot about it. There has been an issue with the default libs from dart not supporting all states necessary to handle reconnection. This has been an issue right from the beginning. Can you use RawSockets?

malibu1966 commented 1 year ago

I don't know, I have never tried rawsockets. I have around four different directories and three different auth methods. Can I do that with rawsockets? I also have a Swift iOS app and a python application and a bunch of python microservices that are already working with this server. It may just be easier for me to trigger on an onConnectionLost and handle reconnects manually.

konsultaner commented 12 months ago

@malibu1966 there is a new version that has a fully rewritten code for reconnect. can you try this?

malibu1966 commented 12 months ago

Yes in the end i did learn streams a lot better. I figured out early on that i needed to use a for loop. But if i recall correctly i had tried to use a try catch to handle an abort message within the loop and the try catch had a break but it wasn't triggering correctly.

On Tue, Sept 19, 2023, 5:27 p.m. Richard @.***> wrote:

@malibu1966 https://github.com/malibu1966 there is a new version that has a fully rewritten code for reconnect. can you try this?

— Reply to this email directly, view it on GitHub https://github.com/konsultaner/connectanum-dart/issues/55#issuecomment-1726421593, or unsubscribe https://github.com/notifications/unsubscribe-auth/AQOQRRPXBY43FQYXN4SI5XLX3H53JANCNFSM6AAAAAAV4HG6WM . You are receiving this because you were mentioned.Message ID: @.***>