kerimamansaryyev / dart_pusher_channels

MIT License
48 stars 13 forks source link

Compatible with Laravel Websockets and Flutter Laravel Echo? #15

Closed nicolasvahidzein closed 2 years ago

nicolasvahidzein commented 2 years ago

Thank you for this package that was recommended to me but is it compatible with laravel echo and laravel websockets on my own server and using a pusher replacement service?

I don't have a key to put in the options.

If you please have a sample config to use for this setup i would be very grateful.

nicolasvahidzein commented 2 years ago

also should we use .ws or .wss to connect?

kerimamansaryyev commented 2 years ago

Hello, if you have the security context on your server use wss, otherwise - use ws. The same thing why http and https are different.

kerimamansaryyev commented 2 years ago

Thank you for this package that was recommended to me but is it compatible with laravel echo and laravel websockets on my own server and using a pusher replacement service?

I don't have a key to put in the options.

If you please have a sample config to use for this setup i would be very grateful.

@nicolasvahidzein I've read the issue and added a feature to add custom path to PusherChannelOptions if you don't have key, key is now nullable. Set your endpoint path and set key to null.

Important: Also, I've updated API reference in version 0.2.8 just today (which is an inline code reference) on pub.dev. Link to API reference of PusherChannelOptions.

So this may be your case:

// If your server is `wss`
const withPath = PusherChannelOptions.wss(
          host: 'example.com',
          path: '/CUSTOM_PATH',
          port: 443,
          key: null,
          protocol: 7);
// If your server is `ws`
const withPath = PusherChannelOptions.ws(
          host: 'example.com',
          path: '/CUSTOM_PATH',
          port: 12,
          key: null,
          protocol: 7);

Pay an attention to /CUSTOM_PATH - it's you endpoint to web socket connection (alternative to /app/[KEY]). If you do not have path set - path:'/'

For the other samples, please check the test file (group with name "Pusher Channel options uri must be convinient").

I've described and tested most of the use-cases

kerimamansaryyev commented 2 years ago

@nicolasvahidzein By the way, my partner developer used own Laravel server as host. So, I think yes - if your laravel library supports Pusher protocol - it's okay.

Specifically, in this client library I used web_socket_channel for WebSocketChannelConnectionDelegate under the hood.

kerimamansaryyev commented 2 years ago

@nicolasvahidzein

Additionally, here is a link to API reference of the default constructor of PusherChannelOptions. It now has detailed description of the parameters.

If these comments helped you - I will close the issue. Please, inform me about your progress.

nicolasvahidzein commented 2 years ago

Hello @mcfugger I upgraded to 0.3.0 and i'm getting a message saying that:

'PusherChannelOptions' is deprecated and shouldn't be used. The class was renamed to [PusherChannelsOptions].. Try replacing the use of the deprecated member with the replacement.

I wrote it like this:

                PusherChannelOptions options = PusherChannelOptions(
                    scheme: 'ws',
                    key: null,
                    host: websocketsDomain,
                    path: websocketsAuthEndpointRelativeUrl,
                    port: websocketsPort,
                    protocol: 7,
                    version: '7.0.3',
                    cluster: websocketsCluster,
                );
nicolasvahidzein commented 2 years ago

Please disregard previous comment, i had missed the added "s" in the variable name.

Will test right now.

kerimamansaryyev commented 2 years ago

@nicolasvahidzein Look, you should read API reference. path is actually not the authentication endpoint, it is endpoint to your connection.

PusherChannelsOptions take those parameters to generate a url for connection.

Typically, pusher channels urls look like this: [scheme]://[host]:[port]/app/[key] If you provide cluster, the generated url will be like: [scheme]://ws-[cluster].[host]:[port]/app/[key] But you said that you don't have key, so I added feature to set custom path, so if you provide path, your url will be generated like this: [scheme]://ws-[cluster_name].[host]:[port][path]. Note: There is no /(slash) by default in front of the path - you should add it when you provide path Like this:

...
path: /[YOUR_PATH]
...

How to know what url is generated by PusherChannelsOptions?

// Print it
print(options.uri);

Or take a look at test file (group with name "Pusher Channel options uri must be convinient"). There are a lot of samples compared with its expected values. For example (from test file):

const withPath = PusherChannelsOptions(
          scheme: 'ws',
          host: 'example.com',
          path: '/CUSTOM_PATH',
          port: 12,
          key: null,
          protocol: 7);

      expect(
          withPath.uri,
          Uri.parse(
              'ws://example.com:12/CUSTOM_PATH?client=dart&version=$kDartPusherChannelsLibraryVersion&protocol=7'));
kerimamansaryyev commented 2 years ago

path is not an endpoint for authentication - it's alternative if you don't have key.

kerimamansaryyev commented 2 years ago

@nicolasvahidzein Please, inform me about your progress in this thread. We will make it through :-)

nicolasvahidzein commented 2 years ago

path is not an endpoint for authentication - it's alternative if you don't have key.

i'm confused, so what do i put in path then? I'll paste here all my values and you can tell me what i should use.

kerimamansaryyev commented 2 years ago

@nicolasvahidzein Okay, replace your original host with example.com to keep it safe here. If you provide your full link, I will tell

nicolasvahidzein commented 2 years ago

const String websocketsAppIdDev = 'RTYG-4852-EFTG-1234'; const String websocketsDomainDev = '192.168.4.8'; const String websocketsAuthEndpointDomainDev = 'http://192.168.4.8:4521'; const String websocketsAuthEndpointRelativeUrlDev = '/broadcasting/auth'; const int websocketsPortDev = 3009; const String websocketsClusterDev = 'mt1'; const bool websocketsEncryptedDev = false;


                PusherChannelsOptions options = PusherChannelsOptions(
                    scheme: 'ws',
                    key: null,
                    host: websocketsDomainDev,
                    path: websocketsAuthEndpointRelativeUrlDev,
                    port: websocketsPortDev,
                    protocol: 7,
                    version: '7.0.3',
                    cluster: websocketsClusterDev,
                );

                PusherChannelsClient pusher = PusherChannelsClient.websocket(
                    reconnectTries: 3,
                    options: options,
                    onConnectionErrorHandle: (error, trace, refresh) {
                        print('websocket error: $error');
                    },
                );

                //TURNED INTO A CLASS VARIABLE
                //Channel? channel;

                //TURNED INTO A CLASS VARIABLE
                //StreamSubscription? eventSubscription;

                pusher.onConnectionEstablished.listen((_) async {

                    //connect to a private channel
                    channel ??= pusher.privateChannel(
                        'user.${myUserId!}',
                        // This is a default authorization delegate
                        // to authorize to the channels
                        // You may implement your own authorization delegate
                        // implementing [AuthorizationDelegate] interface
                        // Use 'http' or 'https' scheme
                        TokenAuthorizationDelegate(
                            authorizationEndpoint: Uri.parse(websocketsAuthEndpointDomain + websocketsAuthEndpointRelativeUrl),
                            headers: {
                                'Authorization':  'Bearer ' + accessToken,
                                'Content-Type': 'application/json',
                                'Accept': 'application/json'
                            }
                        )
                    );

                    await eventSubscription?.cancel();

                    // Ensure you bind to the channel before subscribing, otherwise - you will miss some events
                    eventSubscription = channel?.bind('UserStatusChange').listen((event) {
                        //listen for events form the channel here

                        print(event);

                    });

                    channel!.subscribe();

                });

                //connect the service
                pusher.connect();
nicolasvahidzein commented 2 years ago

And if i replace the values to make it easy to read:

PusherChannelsOptions options = PusherChannelsOptions(
    scheme: 'ws',
    key: null,
    host: '192.168.4.8',
    path: '/broadcasting/auth',
    port: 3009,
    protocol: 7,
    version: '7.0.3',
    cluster: 'mt1',
);
kerimamansaryyev commented 2 years ago

What is RTYG-4852-EFTG-1234?

nicolasvahidzein commented 2 years ago

App ID set in laravel websockets. Not sure if it's useful.

kerimamansaryyev commented 2 years ago

To make it clear, are you sure you using Pusher protocol?

nicolasvahidzein commented 2 years ago

Yes sir. This is how i was connecting using laravel echo and laravel pusher flutter packages:

PusherOptions options = PusherOptions(
                    host: websocketsDomain,
                    port: websocketsPort,
                    cluster: websocketsCluster,
                    encrypted: websocketsEncrypted,
                    auth: PusherAuth(
                        websocketsAuthEndpointDomain + websocketsAuthEndpointRelativeUrl,
                        headers: {
                            'Authorization':  'Bearer ' + accessToken,
                            'Content-Type': 'application/json',
                            'Accept': 'application/json'
                        },
                    ),
                );

                LaravelFlutterPusher pusher = LaravelFlutterPusher(
                    websocketsAppId,
                    options,
                    enableLogging: true,
                );

                _echo = Echo(
                    broadcaster: EchoBroadcasterType.Pusher,
                    client: pusher,
                );

                pusher.connect(onConnectionStateChange: (ConnectionStateChange event) {

                    print('event:');
                    print(event);
                    print(event.currentState);

                    if (event.currentState == 'CONNECTED') {

                        _appStore!.setEcho(_echo);

                        _appStore!.setWebsocketConnection(true);

                        _appStore!.incrementWebsocketConnectionAttempt();

                    } else if (event.currentState == 'DISCONNECTED') {

                        _appStore!.setWebsocketConnection(false);

                    }

                }).onError((error, stackTrace) {
                    print('websocket error: $error');
                });

                //connect to the private channel
                //you can piggy back channel listeners if you are adding more
                _echo!.private('user.${myUserId!}').listen('UserStatusChange', (event) {

                    print(event);

                });
kerimamansaryyev commented 2 years ago

Okay, try this (removed the cluster) (removed the version because it's set as default with the version of the library)

PusherChannelsOptions options = PusherChannelsOptions( scheme: 'ws', path: '', host: '192.168.4.8', port: 3009, protocol: 7, );

nicolasvahidzein commented 2 years ago

ok doing it now. This below looks right to you? The URI?

channel ??= pusher.privateChannel(
'user.${myUserId!}',
TokenAuthorizationDelegate(
    authorizationEndpoint: Uri.parse('http://192.168.4.8:4521/broadcasting/auth'),
    headers: {
        'Authorization':  'Bearer ' + accessToken,
        'Content-Type': 'application/json',
        'Accept': 'application/json'
    }
)
);
kerimamansaryyev commented 2 years ago

Okay, try this (removed the cluster) (removed the version because it's set as default with the version of the library) And set path to ''

PusherChannelsOptions options = PusherChannelsOptions(
    scheme: 'ws',
    path: '',
        key:null,
    host: '192.168.4.8',
    port: 3009,
    protocol: 7,
);
kerimamansaryyev commented 2 years ago

So this library was made accoring to the client API documentation.

Did you test your server in browser? If yes, then what url you used to test your web socket server? Typically, they look like this: ws://ws-ap1.pusher.com:80/app/APP_KEY?client=js&version=7.0.3&protocol=5

What yours looks like?

nicolasvahidzein commented 2 years ago

websocket error: WebSocketChannelException: WebSocketChannelException: WebSocketException: Connection to 'http://192.168.2.5:3009?client=dart&version=0.2.9&protocol=7#' was not upgraded to websocket

nicolasvahidzein commented 2 years ago

laravel websockets you connect to the server directly and authentify using the domain and authentication relative path.

there is no ws link involved.

nicolasvahidzein commented 2 years ago

laravel websockets you connect to the server directly and authentify using the domain and authentication relative path.

there is no ws link involved.

kerimamansaryyev commented 2 years ago

I guess, I know what's the case. I think it's because of the query parameters. Let me fix it in 10 minutes. I will add option to omit them.

nicolasvahidzein commented 2 years ago

ok. But i'm curious, others are using your package as is with laravel websockets on a custom server so how are they doing it? Your partner is achieving this how, do you know?

nicolasvahidzein commented 2 years ago

we also need to add the auth endpoints too i think.

kerimamansaryyev commented 2 years ago

ok doing it now. This below looks right to you? The URI?

channel ??= pusher.privateChannel(
'user.${myUserId!}',
TokenAuthorizationDelegate(
  authorizationEndpoint: Uri.parse('http://192.168.4.8:4521/broadcasting/auth'),
  headers: {
      'Authorization':  'Bearer ' + accessToken,
      'Content-Type': 'application/json',
      'Accept': 'application/json'
  }
)
);

Here is where you can provide auth endpoint. This feature was added in very first versions :)

kerimamansaryyev commented 2 years ago

ok. But i'm curious, others are using your package as is with laravel websockets on a custom server so how are they doing it? Your partner is achieving this how, do you know?

See the closed issues of this library, the folks may help you with that, I will ask my teammate about it too

kerimamansaryyev commented 2 years ago

The 0.3.1 is live now.

If you add shouldSupplyQueryMetaData: false. It will omit query parameters. Try it please.

const options = PusherChannelsOptions(
         scheme: 'ws',
     path: '',
         key:null,
     host: '192.168.4.8',
      port: 3009,
      protocol: 7,
          shouldSupplyQueryMetaData: false);
kerimamansaryyev commented 2 years ago

@nicolasvahidzein Test the new version with as described above, please.

kerimamansaryyev commented 2 years ago

The 0.3.1 is live now.

If you add shouldSupplyQueryMetaData: false. It will omit query parameters. Try it please.

const options = PusherChannelsOptions(
         scheme: 'ws',
   path: '',
         key:null,
   host: '192.168.4.8',
    port: 3009,
    protocol: 7,
          shouldSupplyQueryMetaData: false);

@nicolasvahidzein Try this

kerimamansaryyev commented 2 years ago

@nicolasvahidzein I will be waiting for the updates in your progress. Keep the thread up-to-date, we will go through it.

nicolasvahidzein commented 2 years ago

ok testing now

nicolasvahidzein commented 2 years ago

WebSocketException: Connection to 'http://192.168.2.5:3009#' was not upgraded to websocket

nicolasvahidzein commented 2 years ago

it added a pound symbol but i think we are close.

kerimamansaryyev commented 2 years ago

@nicolasvahidzein No, pound symbol is part of an exception. You must now check if your host server was upgraded to web sockets or try different port. When I had this exception with my project on test server - I tried always port 12.

The issue on server side.

nicolasvahidzein commented 2 years ago

I am not seeing any connection attempt on the server, maybe i am looking in the wrong place. Is there another place besides my laravel websocket process i can check?

kerimamansaryyev commented 2 years ago

Try to upgrade your server to ws or wss. What do you use nginx or Apache?

I am not a server-side developer. These answers might be helpful: https://stackoverflow.com/a/72135334/12177707

I would recommend also those things:

  1. Make sure your scheme. ws or wss
  2. Check if your port is right. From my experience - I used to have similiar exception. My partner said to use port 3009, but it kept giving exceptions. Then I used port 12 (ws schemes usually use 12 port, wss 443)
nicolasvahidzein commented 2 years ago

i'm using port 3009 for sure. No doubt about that as it's working with laravel_flutter_pusher.

kerimamansaryyev commented 2 years ago

laravel_flutter_pusher based on native plugins, so my guess is that they make connections over http/https schemes. This library tries to connect using ws/wss schemes (web_socket_channel) and your server does not allow connection because the server was not upgraded to web sockets (ws/wss) as the exception claims.

nicolasvahidzein commented 2 years ago

yikes. This is a problem. I wonder how Yassine did it. I will wait for him to reply to my email and ask him and revert back here.

Thanks a lot @mcfugger i'll update you here. All your hard work and wonderful plugin are appreciated.

kerimamansaryyev commented 2 years ago

Thank you very much! You are welcome. I will keep the issue open unless assumption is confirmed. It might be helpful: https://stackoverflow.com/a/72135334/12177707

Will be waiting for updates here.

nicolasvahidzein commented 2 years ago

I should mention that i am using an older version of laravel websocket v1.1 on Laravel v 5.8. Not sure if that is the issue.

kerimamansaryyev commented 2 years ago
....
GET / HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: SOME_KEY
....
Origin: http://example.com

@nicolasvahidzein Do you know where to find these configs on a server?

I think the line makes sense:

Upgrade: websocket
kerimamansaryyev commented 2 years ago

@nicolasvahidzein And check this informative answer too: https://stackoverflow.com/questions/66597039/laravel-websocket-wont-connect-on-production

nicolasvahidzein commented 2 years ago

also i think i am using pusher v5 since my version of laravel websockets is so old.

nicolasvahidzein commented 2 years ago

I spoke to one of your users Yassine who was very helpful. I guess i need to upgrade my packages (websockets and pusher) and then implement your package. No choice. My versions are too old.

kerimamansaryyev commented 2 years ago

@nicolasvahidzein It's a power of community :) Tell me when you make it with the new version of Laravel - then I will close the issue