pusher / pusher-websocket-dotnet

Pusher Channels Client Library for .NET
MIT License
112 stars 114 forks source link

pusher:ping not being sent #152

Open aijorgenson opened 6 months ago

aijorgenson commented 6 months ago

I have a web app using the pusher library that regularly sends pusher:ping requests ( https://pusher.com/docs/channels/library_auth_reference/pusher-websockets-protocol/#ping-and-pong-messages ); however, my .NET app is not sending these requests when subscribed to the same channels.

Per the Pusher documentation, it seems like this should be an automatic thing. I'm not able to find anything in the documentation for this package around it.

I have tried both 2.1 and the beta build of the package.

conceicaomateus commented 4 months ago

Hi!

I'm having the same problem with pusher:ping.

maiconghidolin commented 4 months ago

Same here

sundayz commented 4 months ago

I have the same problem with a Laravel Reverb server. If I understand the Pusher docs correctly, the pusher:ping event is used when the WebSocket implementation doesn't support ping/pong messages? So maybe the problem is that the server isn't detecting that the client supports native websocket pings.

Erutan409 commented 1 week ago

For those (and maybe even more applicable to @sundayz) who are still having this issue, you'll need to modify the library yourself. You'll need to add a pusher event for pong when the server sends pings to inactive connections (this library in this case).

Add some constants for the messages:

namespace PusherClient
{
    class Constants
    {
        public const string PUSHER_MESSAGE_PREFIX = "pusher";
        public const string ERROR = "pusher:error";

        public const string PING = "pusher:ping"; // <-- add here
        public const string PONG = "pusher:pong"; // <-- add here

        public const string CONNECTION_ESTABLISHED = "pusher:connection_established";

        public const string CHANNEL_SUBSCRIBE = "pusher:subscribe";
        public const string CHANNEL_UNSUBSCRIBE = "pusher:unsubscribe";
        public const string CHANNEL_SUBSCRIPTION_SUCCEEDED = "pusher_internal:subscription_succeeded";
        public const string CHANNEL_SUBSCRIPTION_ERROR = "pusher_internal:subscription_error";
        public const string CHANNEL_MEMBER_ADDED = "pusher_internal:member_added";
        public const string CHANNEL_MEMBER_REMOVED = "pusher_internal:member_removed";

        public const string INSECURE_SCHEMA = "ws://";
        public const string SECURE_SCHEMA = "wss://";

        public const string PRIVATE_CHANNEL = "private-";
        public const string PRESENCE_CHANNEL = "presence-";
    }
}

Add the pong event:

using Newtonsoft.Json;

namespace PusherClient
{
    internal class PusherPongEvent
    {
        public PusherPongEvent()
        {
        }

        [JsonProperty(PropertyName = "event")] public string Event { get; } = Constants.PONG;
    }
}

Remove the auto-ping configuration:

public async Task ConnectAsync()
{
    _connectionSemaphore = new SemaphoreSlim(0, 1);
    try
    {
        _currentError = null;
        if (_pusher.PusherOptions.TraceLogger != null)
            _pusher.PusherOptions.TraceLogger.TraceInformation($"Connecting to: {_url}");

        ChangeState(ConnectionState.Connecting);

        _websocket = new WebSocket(_url); // <-- removed unnecessary configuration

        await Task.Run(() =>
        {
            _websocket.Error += WebsocketConnectionError;
            _websocket.MessageReceived += WebsocketMessageReceived;
            _websocket.Open();
        }).ConfigureAwait(false);

        var timeoutPeriod = _pusher.PusherOptions.InnerClientTimeout;
        if (!await _connectionSemaphore.WaitAsync(timeoutPeriod).ConfigureAwait(false))
            throw new OperationTimeoutException(timeoutPeriod, Constants.CONNECTION_ESTABLISHED);

        if (_currentError != null) throw _currentError;
    }
    finally
    {
        if (_websocket != null) _websocket.Error -= WebsocketConnectionError;

        _connectionSemaphore.Dispose();
        _connectionSemaphore = null;
    }
}

If you leave the default constructor configuration, it doesn't seem to hurt anything, but probably still a good idea to remove it.

Then add the handling of the ping message, which sends the pong message back:

private void ProcessPusherEvent(string eventName, string rawJson, Dictionary<string, object> message)
{
    var messageData = string.Empty;
    if (message.ContainsKey("data")) messageData = (string)message["data"];

    switch (eventName)
    {
        case Constants.CONNECTION_ESTABLISHED:
            ParseConnectionEstablished(messageData);
            break;

        case Constants.PUSHER_SIGNIN_SUCCESS:
        case Constants.PUSHER_WATCHLIST_EVENT:
            EmitEvent(eventName, rawJson, message);
            break;

        case Constants.PING: // <-- added
            SendAsync(DefaultSerializer.Default.Serialize(new PusherPongEvent())).ConfigureAwait(false);
            break;
    }
}
aijorgenson commented 1 week ago

I just ended up creating a channel that anyone authenticated can subscribe to, and then I send periodic "ping" events to keep the connection alive.

I could be wrong, but I would speculate that Pusher is intentionally not accommodating to projects that choose to self host their own websocket server.

Erutan409 commented 1 week ago

I could be wrong, but I would speculate that Pusher is intentionally not accommodating to projects that choose to self host their own websocket server.

Agreed

sundayz commented 1 week ago

For those using the Laravel Reverb websocket server, a fix has been merged on the server side: https://github.com/laravel/reverb/issues/224