Coder Social home page Coder Social logo

konsultaner / connectanum-dart Goto Github PK

View Code? Open in Web Editor NEW
21.0 6.0 14.0 728 KB

This is a WAMP client (Web Application Messaging Protocol) implementation for the dart language and flutter projects.

License: MIT License

Dart 100.00%
wamp wamp-client dart-language

connectanum-dart's Introduction

Konsultaner

I've been a software developer for about 25 years now and 15 years as a full-stack professional. I love coding. I love clean code. I love simplicity. I love to have everything running as smooth as possible. Just like everyone here 😄

I'm running the company Konsultaner with my buddy and love doing it ever since. We produce software like simple websites, apps, web applications, middleware applications, database tasks, infastructure with ansible, ai stuff and custom ERP systems. I like to open source as much as possible and love contibuting to projects I use in my daily work.

My latest tech stack looks like this

connectanum-dart's People

Contributors

cydrickn avatar imranhakeem avatar joelbm24 avatar konsultaner avatar ksdaemon avatar liquidiert avatar oberstet avatar solid-yuriiprykhodko avatar yurii-prykhodko-solid avatar

Stargazers

 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

connectanum-dart's Issues

Need extra exports from package

Hi. I would like to have, that following dart files are exported from package:

import 'package:connectanum/src/message/goodbye.dart';
import 'package:connectanum/src/message/abstract_message.dart';
import 'package:connectanum/src/serializer/abstract_serializer.dart';

Currently "Dart analysis" shows for me warning, when I import these files. So it would be nice if connectanum.dart file also exported these files.

Client close required to close unauthorised session on reconnect!

I was connecting to Client without authid and ticket. After some time i was doing reconnect with authid. But it didn't worked. And it is because, i already have anonymous client session open. Wamp doesn't understand how to change between anonymous session to authorised, without session close. So for my project session.close() is crucial.

Unhandled Exception: NoSuchMethodError: The method '>' was called on null.

I got this error:

E/flutter ( 3068): #0      Object.noSuchMethod (dart:core-patch/object_patch.dart:53:5)
E/flutter ( 3068): #1      WebSocketTransport.receive.<anonymous closure> (package:connectanum/src/transport/websocket/websocket_transport_io.dart:91:29)

Looks like this happens in this package line:
if (_socket.closeCode > 1000 && !_goodbyeSent && !_goodbyeReceived) {

Add trace to info logging

It should be possible to trace debug the client. Logging should be added through out the entire code.

WAMPCRA: Only derive key when `salt` present in Challenge

When using WAMPCRA, if the server does not send a salt in the challenge the Client should not try to derive the key, this is broken. It seems connectanum-dart does not check if salt is present in server's response and always tries to derive the key. This of course means the authentication fails

Here is the relevant code

if (extra.iterations != null && extra.iterations > 0) {

Support for WAMP Cryptosign authentication

Are there plans to support WAMP Cryptosign as authentication method (for clients)?

fwiw, I'm working on a project that considers Flutter .. and consequently connectanum, but we need wamp-cryptosign ...

Strange behaviour of Session.publish and/or Session.subscribe

It looks like that there is something strange with Session.publish and/or Session.subscribe. It might also be possible that something is off in my router implementation. Sorry, if this is the case.

The following program

  final subscription = await session1.subscribe(
      'topic');
  subscription.eventStream!.listen(
      (event) => print('received from <id>.push: \'${event.arguments![0]}\''));
  // ...
  print('before publish: 1');
  await session2.publish(
      'topic',
      arguments: ['1'], options: PublishOptions(acknowledge: true));
  print('after publish: 1');

  print('before publish: 2');
  await session2.publish(
      'topic',
      arguments: ['2'], options: PublishOptions(acknowledge: true));
  print('after publish: 2');

  print('before publish: 3');
  await session2.publish(
      'topic',
      arguments: ['3'], options: PublishOptions(acknowledge: true));
  print('after publish: 3');

  print('before publish: 4');
  await session2.publish(
      'topic',
      arguments: ['4'], options: PublishOptions(acknowledge: true));
  print('after publish: 4');

is generating the following output

before publish: 1
after publish: 1
before publish: 2
received from <id>.push: '1'
after publish: 2
before publish: 3
received from <id>.push: '2'
after publish: 3
before publish: 4
received from <id>.push: '3'
after publish: 4

It looks like that somehow the first publish is not directly triggering the subscription handler. Also the last event seem to not trigger the subscriber. It looks somehow like that a new publish just triggers the reception of an older event.

Expected output

before publish: 1
received from <id>.push: '1'
after publish: 1
before publish: 2
received from <id>.push: '2'
after publish: 2
before publish: 3
received from <id>.push: '3'
after publish: 3
before publish: 4
received from <id>.push: '4'
after publish: 4

Any ideas?

Better reconnection handling

Copy pasted main points from #47 for context:

So in the first run, there are the next points of connection failure and reconnection:

  • Transport failures before session is established.
  • Wamp abort (due to auth or other reasons) before session is established. Wamp error means that server processed client messages and get to some point that resolves in abort. Does auto reconnection fix this? Seems that not.
  • Transport failures during session. Definitely the most important point to proceed with reconnection (if it is configured)
  • Wamp failures during session. Seems that this situations should not lead to reconnection. As they happens when something is broken on business logic side (like using feature that is not announced, using serializer that is not supported etc), so autoreconnection won't automatically fix them.

Incompatibility error with latest Flutter version

When i tried to run flutter upgrade command, error appeared:

Because every version of flutter_test from sdk depends on meta 1.1.8 and every version of connectanum from git depends on meta ^1.2.2, flutter_test from sdk is incompatible with connectanum from git.
So, because smartmex depends on both connectanum from git and flutter_test any from sdk, version solving failed.

Session call arguments type

I think session.call arguments should allow List<dynamic>? to be set as type, instead of just List<Object>?, as it is currently.

PS: Very happy to see, that there is null safe version right now. Big thanks for that.

Support for dynamic reconnectTime in method `connect`

Would it be good to support dynamic reconnectTime when calling the connect function?

For instance there is a scenario that one needs to retry 10 times when the network is suddenly disconnected.

The initial reconnectTime is set to 1 second. However in each retry this reconnectTime is increased by 1s.

For example, it will only take 1 second in the first retry. In the next retry it will take 2s. In the nth retry, it will take nth seconds.

And if possible, this reconnectTime is better to be set by users who pass a function to dynamically calculate it.

Currently from what I read, the reconnectTime can be set once only. Is there a way to do this?

Add way more logging!

I need more logging though out the whole package to track bugs and issues with the protocoll or messages that have been sent.

Currently the connection doesn't support authextra

Currently Client does not support additional field authextra

Where the current Client only supports arguments:

  • transport
  • authid
  • authroles
  • realms
  • authenticationMethods
  • isolateCount

In this issue, I'm looking forward to support the authextra
authextra: If provided, this data is passed to the router during the connection, which the router may use to further customize the challenge to the client.

This is authextra is documented here: https://github.com/crossbario/autobahn-js/blob/master/doc/reference.md
Screenshot 2023-08-22 at 11 02 29 PM

Support for retention of event messages emitted from Publisher and retrieve from Subscriber end

It will be good if 'Event History' feature is implemented in dart.

Like when publishing an event, the publisher can set an option (retain=True) in the PublishOptions which will cause the Broker to retain the event being published as the most-recent event on this topic.

Same upon subscription, a client can pass (get_retained=True) in the SubscribeOptions. If there is any retained event, it will be immediately sent to the subscriber.

Reference: https://crossbar.io/docs/Event-History/

Python implementation: https://github.com/crossbario/autobahn-python/tree/master/examples/twisted/wamp/pubsub/retained

Is there a way to recreate Client?

Currently at the beginning i am logging in unauthorised. At some point i am calling reconnect method, where i create client again. Client is created without any errors. But after that, when i make session call method request, it leads to this error:

    Session.call (package:connectanum/src/protocol/session.dart:192:9)<asynchronous suspension>
    #1      WampService.call (package:smartmex/src/services/WampService.dart:49:25)
    #2      LoginView.continueValidation.<anonymous closure>.<anonymous closure> (package:smartmex/src/screen/Login.dart:95:23)
    #3      _rootRunUnary (dart:async/zone.dart:1134:38)
    #4      _CustomZone.runUnary (dart:async/zone.dart:1031:19)
    #5      _CustomZone.runUnaryGuarded (dart:async/zone.dart:933:7)
    #6      _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:338:11)
    #7      _BufferingStreamSubscription._add (dart:async/stream_impl.dart:265:7)
    #8      _SyncBroadcastStreamController._sendData (dart:async/broadcast_stream_controller.dart:377:20)
    #9      _BroadcastStreamController.add (dart:async/broadcast_stream_controller.dart:252:5)
    #10     _AsBroadcastStreamController.add (dart:async/broadcast_stream_controller.dart:476:11)
    #11     _rootRunUnary (dart:async/zone.dart:1134:38)
    #12     _CustomZone.runUnary (dart:asy

My reconnect method looks like this:

  void reconnect(String authid) {
    print('wamp reconnect $authid');
    disconnect();
    connectWampClient(authid);
  }

In disconnect method currently i am just nulling session, till there will be method available, to also close session.

I am creating Client with Ticket auth:

    Client client = Client(
      realm: "myRealm",
      transport: WebSocketTransport(
          "wss://myWs.lv/cws",
          new Serializer(),
          WebSocketSerialization.SERIALIZATION_JSON
      ),
      authId: authId,
      authenticationMethods: authId == null ? null : [TicketAuthentication(authId)],
    );

Is there a way to send options when subscribe?

In line 27 of example/main.dart, it demonstrates an example of how to subscribe to a topic:

final subscription = await session.subscribe(topic);

But what about sending options when subscribe?

I want to pass a Map like {"a": 123} as an option to the server as it is required.

Is there a way to do this?

I see that there is a second parameter SubscribeOptions that has three variables: match, meta_topic and get_retained, but have no idea what they are.

WAMP IDL support

Ok, this is an advanced, more complex topic. Let me see if I can find a way to explain it properly. Bare with me, you'll need some patience;) Let me assure you: it's worth it. You will see the light;) Hopefully. Maybe also wrong to explain all that here on the issue tracker for connectanum-dart. It'll be long, multiple comments. Anyways, here we go.

Intro and the Why?

WAMP was designed along a dynamic typing discipline from the beginning (eg even in WAMPv1). This was a deliberate and conscious decision, and it has served us (the WAMP community) well .. I'd say.

What that means is: WAMP application payload in RPC call arguments/results and PubSub events can transport any args (positional payload) and kwargs (keyword payload).

Full run-time dynamic typing has advantages (very flexible, no-frills get started right away, etc), but - as with anything - there is a price to pay.

The price to pay comes in different shapes.

Robustness

Robust app code will need quite a lot of run-time checks. eg say you have a registered WAMP procedure proc add2(a, b) that returns the (numeric?) sum of a and b (two positional args). what if the caller calls your proc with a=="foo" and b==666?

JS, being a duck, will happily return foo666. most of the time this won't be matching what neither the caller nor the callee had in mind.

so you would continue adding "run-time checks" to the implementation of add2. as in: "if type(a) != int then raise .."

This can get boring quite quick, and of course is error prone. did you really check everything? what if the caller send three positional args? etc etc

Code scalability

With one developer doing both sides of a WAMP API, eg the app code for the caller and callee, or the publisher and the subscriber, everything is in the head of 1 person, and fixing stuff and making sure it matches up is straight forward and natural. You can do it.

Now, what if you have 10 developers? In one organization this can get tricky to manage already. If you have developers of different parties involved, it quickly turns into a big problem and time sucker. Was it a,b being ints or also floats? Ah, you changed it, so now it's a,b,c?

How do you even communicate what one side (eg callee) did intend anyways? Is it communicated in a way that even gives the other side (eg caller) a chance to do the right thing?

Documentation

Ok, so now your problem becomes: add2 (the callee side) must have proper docs. Sure, dream on! ;) The problem is manyfold: can you even precisely write natural language docs? even if so on day 1, now add2 changes. Surely, updating the docs is forgotten. Now the situation is even worse: there is docs, but they are wrong. So a caller that wants to do its best can't even. And so on. Wrong docs is worse than no docs.

How to ensure that a proc that claims in the docs it takes exactly 2 positional args of type int does indeed at the technical level?

Statically typed host languages

Dealing with dynamic types and data in statically typed host languages always incurs some form of impedance mismatch. Kinda expected.

Also, when working in such a language, you naturally want native language static types for your app level objects.

Eg say you have

public class Person {
    public String firstname;
    public String lastname;
    public String department;

    public Person() {
        this.firstname = "unknown";
        this.lastname = "unknown";
        this.department = "unknown";
    }

    public Person(String firstname, String lastname, String department) {
        this.firstname = firstname;
        this.lastname = lastname;
        this.department = department;
    }
}

in your app.

Now, wouldn't it be great if you could do (pseudocode):

Person someone = await session.call("com.example.get_last_customer");
print(someone.lastname)

Performance

WAMP supports multiple, pluggable serialization formats (JSON, MsgPack, CBOR, ...) and routers transparently convert between those (roundtripping). Very useful, all cool. We have that today. However ..

  • In Crossbar.io, CPU cycles spent in dealing with this are top 1 and dominate (besides WebSocket low-level processing)
  • WAMP clients also have to serialize/deserialize the whole payload even if they only need access to a tiny part of the payload (eg thing of a single attribute in a deeply nested dict)
  • further, this is not only about CPU cycles, but also (and even more important) about memory and memory pressure. Dynamic typing is .. dynamic also allocation wise. By nature.

There are more problems .. I stop here;)

Is there planed builder?

Currently when i want to create client, with and without authid, i need to wrap whole creation logic inside if condition. With builder i would be able to create client without duplicate code. Currently i am doing it like this:

    if(authId.isEmpty) {
      client = Client(
        realm: "myRealm",
        transport: WebSocketTransport(
            "wss://myws.lv/cws",
            new Serializer(),
            WebSocketSerialization.SERIALIZATION_JSON
        ),
      );
    } else {
      client = Client(
        realm: "myRealm",
        transport: WebSocketTransport(
            "wss://myws.lv/cws",
            new Serializer(),
            WebSocketSerialization.SERIALIZATION_JSON
        ),
        authId: authId,
        authenticationMethods: [CraAuthentication(authId)],
      );

Expecting something like this:

        if (!authId.isEmpty) {
            builder.withAuthId(authId);
        }

PS: I am doing something like this in my android project, who is using Jawampa. Jawampa also has builder.

Support multiple serializers

Currently a single serializer needs to be provided to the WebSocketTransport class, it would be very useful to optionally allow to provide a list of serializers based on priority. If the router the "speaks" the first one it gets used, otherwise the next is tried.

It's more of an optimization but the binary serializers are much faster than json.

Example Client create not working

final client = Client(
  realm: "my.realm",transport: WebSocketTransport("wss://localhost:8443")
);

If I use this code from example, to create client, i receive error: "Too many positional arguments: 0 expected, but 1 found". WebSocketTransport doesn't have arguments. Are docs outdated? If yes, can they be updated with current usage?
Also. What is correct usage for call and other methods with and without arguments? If it is possible, please update also that one.

session.publish seem to stuck

Hello!

I just discovered WAMP & your dart library. First of all, thank you for this great library. I just started to play with it a bit.

However, I'm quite new to dart and maybe the answer to this issue is more related to something that I'm currently not fully understanding. Sorry, if this is the case.

I'm using connectanum-dart v2.0.1.

I wrote the following test program:

import 'dart:async';
import 'dart:io';

import 'package:connectanum/authentication.dart';
import 'package:connectanum/connectanum.dart';
import 'package:connectanum/json.dart';

void main() async {
  final client1 = Client(
    realm: 'test',
    transport: WebSocketTransport(
      'ws://127.0.0.1:8080',
      Serializer(),
      WebSocketSerialization.SERIALIZATION_JSON,
    ),
    authenticationMethods: [CryptosignAuthentication.fromHex('00' * 32)],
  );
  final session1 = await client1.connect().first;
  final subscription = await session1.subscribe(
      '3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29.push');
  subscription.eventStream!.listen(
      (event) => print('received from <id>.push: \'${event.arguments![0]}\''));

  final client2 = Client(
    realm: 'test',
    transport: WebSocketTransport(
      'ws://127.0.0.1:8080',
      Serializer(),
      WebSocketSerialization.SERIALIZATION_JSON,
    ),
    authenticationMethods: [CryptosignAuthentication.fromHex('00' * 32)],
  );
  final session2 = await client2.connect().first;

  await session2.publish(
      '3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29.push',
      arguments: ['Hello']);

  await session2.publish(
      '3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29.push',
      arguments: ['World']);

  unawaited(session1.close());
  unawaited(session2.close());
}

The output right now is

received from <id>.push: 'Hello'

For me it looks like that the program gets stuck within the first await session2.publish. The function session2.publish seem to never return.

Is this behaviour expected? Or did I do something wrong? I expected the program to just publish 'Hello' and 'World'.

Expected output

received from <id>.push: 'Hello'
received from <id>.push: 'World'

Sorry, if this behaviour is somehow related to my quiet limited dart knowledge.

Custom HttpHeaders

Is it possible to send custom HttpHeaders? For my project there is need to specify them.

Unable to catch error

Currently im was trying to catch and error such as wamp.error.no_such_procedure wamp.error.no_such_subscription
But I was unable to catch this error. Noting happen in onError of the stream.

Example code:
this example is when the session already connected

session.call('no-procedure').listen(
  (result) => print(result.arguments![0]),
  onError: (e) {
    // it does not went here even the procedure is not exists
      var error = e as Error; 
      print(error.error);
  }
);

Was there dublicate sessions fix from version 1.0.6 to 1.1.6

We saw, that one user with iPhone and "connectanum-dart" version 1.0.6 was making duplicate sessions. This issue isn't happening for any phones on version 1.1.6. Is it coincidence and there is chance, that this may occur or there was related fix during latest releases regarding this issue?

Support dart2wasm compilation

I added a branch that deals with this issue. I got the new package:web to work with the default compiler. To get it all run with dart2wasm pointy castle needs to support wasm compilation as well.

bcgit/pc-dart#221

Session.call seem to block

Sorry, for all these issues!

I was just trying to call a registered function. This is mainly based on your examples shown in the README. It looks like that the code get stuck during the reception of the result. I'm using crossbar v21.3.1.

void main() async {
  final client1 = Client(
    realm: 'test',
    transport: WebSocketTransport(
      'ws://127.0.0.1:8080',
      Serializer(),
      WebSocketSerialization.SERIALIZATION_JSON,
    ),
    authenticationMethods: [CryptosignAuthentication.fromHex('00' * 32)],
  );
  final session1 = await client1.connect().first;
  final registered = await session1.register('demo.get.version');
  registered.onInvoke((invocation) {
    print('called');
    invocation.respondWith(arguments: ['1.1.0']);
  });
  final client2 = Client(
    realm: 'test',
    transport: WebSocketTransport(
      'ws://127.0.0.1:8080',
      Serializer(),
      WebSocketSerialization.SERIALIZATION_JSON,
    ),
    authenticationMethods: [CryptosignAuthentication.fromHex('00' * 32)],
  );
  final session2 = await client2.connect().first;

  await for (final result in session2.call("demo.get.version")) {
    print("${result.arguments![0]}");
  }
  await for (final result in session2.call("demo.get.version")) {
    print("${result.arguments![0]}");
  }

  unawaited(session1.close());
  unawaited(session2.close());
}

output:

called
1.1.0

That means, the second call is never triggered. The program seem to hang somewhere during the first call.

expected output:

called
1.1.0
called
1.1.0

not connect websocket

void main() async {
// Start a client that connects without the usage of an authentication process
final client1 = Client(
// The realm to connect to
// We choose WebSocket transport
transport: WebSocketTransport(
'wss://...........8080/wss2/',
// if you want to use msgpack instead of JSON just import the serializer
// from package:connectanum/msgpack.dart and use WebSocketSerialization.SERIALIZATION_MSGPACK
Serializer(),
WebSocketSerialization.serializationJson,

{"Sec-WebSocket-Protocol": "wamp"},

));

late Session session1;
try {
// connect to the router and start the wamp layer
session1 = await client1
.connect(
options: ClientConnectOptions(
reconnectCount: 3, // Default is 3
reconnectTime: const Duration(
milliseconds: 200) // default is null, so immediately
// you may add ping pong options here as well
))
.first;

log("Connected>>>");
// if you want to change the options after each reconnect, use this event
client1.onNextTryToReconnect.listen((passedOptions) {
  // enlarge the time to wait after each reconnect by 500ms
  passedOptions.reconnectTime = Duration(
      milliseconds: passedOptions.reconnectTime!.inMilliseconds + 500);
});
// register a method that may be called by other clients
final registered = await session1.register('order');
registered.onInvoke((invocation) => invocation.respondWith());
// subscribe to a topic that my be published by other clients
final subscription = await session1.subscribe('order');
subscription.eventStream!.listen((event) => print(event.arguments![0]));
await subscription.onRevoke.then((reason) =>
    print('The server has killed my subscription due to: $reason'));

} on Abort catch (abort) {
// if the serve does not allow this client to receive a session
// the server will cancel the initializing process with an abort
print(abort.message!.message);
}
runApp(const MyApp());
}

onDisconnect and onConnecting events

Is or there will be client connection events like onDisconnect, onConnecting, onConnected. For example depending on those statuses i want to do specific tasks. If onDisconnect is triggered, i would like to call reconnect method etc.
Also for reconnect i am not seeing client.close() option for cases, when i want to close unauthorised client session, but reconnect with client authid,

Support argon 2 with wamp-scram

This will happen as soon as argon is available for dart. Please let me know if anyone sees a working argon implementation for dart. Link packages in this issue!

Subscription Revocation

As far as i see, there is not implemented Subscription Revocation. Is it planed soon?
Until then, is it just enough, if i am using just cancel method on StreamSubscription?

Implement CBOR serializer

Currently this library only implements json and msgpack serializers for WAMP. Would be good if CBOR was supported as well.

Auth failure response doesn't seem right

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.

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.