supabase-community / supabase-csharp

A C# Client library for Supabase
https://github.com/supabase-community/supabase-csharp/wiki
MIT License
484 stars 50 forks source link

SocketStateEventHandler not being called after reconnection #105

Open GrimLothar opened 1 year ago

GrimLothar commented 1 year ago

Bug report

Describe the bug

For some unknown reason that I'm still trying to debug, my client is losing the connection to the socket. But besides that, once it reconnects, the SocketStateEventHandler is not being called.

Here is the Manager i'm using:

See code ```c# using System.Collections.Generic; using System.Threading.Tasks; using Newtonsoft.Json; using UnityEngine; using Supabase; using Supabase.Models; using Supabase.Realtime; using Supabase.Realtime.Interfaces; using Supabase.Realtime.Models; using Supabase.Realtime.PostgresChanges; using static Supabase.Realtime.PostgresChanges.PostgresChangesOptions; using static Supabase.Realtime.Constants.ChannelEventName; using TcgEngine; using UnityEngine.Events; using Client = Supabase.Client; public class SupabaseManager : MonoBehaviour { public UnityAction onMatchRequest; private const string SupabaseURL = "https://redacted.supabase.co"; private const string SupabasePublicKey = "redacted"; private Client _supabase; private RealtimeBroadcast _userBroadcast; private static SupabaseManager instance; private readonly Dictionary channels = new Dictionary(); private void Awake() { instance = this; } async void Start() { Debug.Log("SupabaseManager start"); string url = SupabaseURL; string key = SupabasePublicKey; if (_supabase == null) { var options = new SupabaseOptions { Headers = new Dictionary { ["Authorization"] = "Bearer " + ApiClient.Get().AccessToken } }; Debug.Log(ApiClient.Get().AccessToken); _supabase = new Client(url, key, options); await _supabase.InitializeAsync(); } _supabase.Realtime.AddDebugHandler((sender, message, exception) => Debug.Log(message)); _supabase.Realtime.AddStateChangedHandler(SocketEventHandler); _supabase.Realtime.SetAuth(ApiClient.Get().AccessToken); var realtimeClient = await _supabase.Realtime.ConnectAsync(); Debug.Log("Is connected? " + realtimeClient.Socket?.IsConnected); } private void OnDestroy() { Debug.Log("SupabaseManager OnDestroy"); if (_supabase != null) { foreach (var channel in channels.Values) { _supabase.Realtime.Remove(channel); } channels.Clear(); _supabase.Realtime.RemoveStateChangedHandler(SocketEventHandler); _supabase.Realtime.Disconnect(); _supabase = null; } } private void PostgresUpdatedHandler(IRealtimeChannel _, PostgresChangesResponse change) { Debug.Log(change); if (change.Payload?.Data?.Table == "users") { //User data changed var model = change.Model(); var user = Authenticator.Get().UserData; if (model != null) { if (model.friends_requests.Length > user.friends_requests.Length) { //New friend request. Notify in UI Debug.Log("New friend request"); user.friends_requests = model.friends_requests; } } } } private void BroadcastEventHandler(IRealtimeBroadcast broadcast, BaseBroadcast state) { if (state != null && state.Event != null) { switch (state.Event) { case "matchRequest": if (state is UserBroadcast userBroadcast && userBroadcast.Payload != null) { //Debug.Log($"Show match invitation: {JsonConvert.SerializeObject(userBroadcast, Formatting.Indented)}"); onMatchRequest?.Invoke(userBroadcast.Payload.MatchRequestFrom, userBroadcast.Payload.Key); return; } break; } } Debug.Log($"Unknown broadcast: {JsonConvert.SerializeObject(state, Formatting.Indented)}"); } private async void SocketEventHandler(IRealtimeClient sender, Constants.SocketState state) { var username = ApiClient.Get().Username; Debug.Log($"Socket is ${state.ToString()} and username is ${username} and user_id is ${ApiClient.Get().UserID}"); channels.TryGetValue("users:" + username, out RealtimeChannel personalUserChannel); if (personalUserChannel == null) { personalUserChannel = sender.Channel("users:" + username); channels.Add("users:" + username, personalUserChannel); _userBroadcast = personalUserChannel.Register(false, true); _userBroadcast.AddBroadcastEventHandler(BroadcastEventHandler); } channels.TryGetValue("public:users", out RealtimeChannel userTableChannel); if (userTableChannel == null) { userTableChannel = sender.Channel("public:users"); channels.Add("public:users", userTableChannel); userTableChannel.Register(new PostgresChangesOptions("public", "users", ListenType.Updates, "id=eq." + ApiClient.Get().UserID )); userTableChannel.AddPostgresChangeHandler(ListenType.Updates, PostgresUpdatedHandler); } if (state == Constants.SocketState.Open) { foreach (var channel in channels.Values) { await channel.Subscribe(); } } else { foreach (var channel in channels.Values) { channel.Unsubscribe(); } } } public async Task SendMatchRequestBroadcast(string username, string key) { channels.TryGetValue("users:" + username, out RealtimeChannel channel); if (channel == null) { channel = _supabase.Realtime.Channel("users:" + username); channels.Add("users:" + username, channel); var broadcast = channel.Register(false, true); broadcast.AddBroadcastEventHandler(BroadcastEventHandler); } var data = new UserBroadcast { Event = "matchRequest", Payload = new UserStatus { MatchRequestFrom = ApiClient.Get().Username, Key = key } }; if (!channel.IsSubscribed) { await channel.Subscribe(); } if (channel.IsJoined) { return await channel.Send(Broadcast, null, data); } else { Debug.LogError("Channel not joined. Try to rejoin?"); } return false; } public static SupabaseManager Get() { return instance; } } ```

And you can see in my logs where the socket gets disconnected (I left the stacktraces for those in case it gives some hints) but after each of the Socket State Change I was expecting to see the Socket is $state and username is $username and user_id is $user_id line and that never happened

Logs ``` SupabaseManager start Socket Reconnection: Initial Socket Connected to: wss://redacted4&vsn=1.0.0 Socket State Change: Open Socket is $Open and username is $Lothar and user_id is $1 Socket Push [topic: phoenix, event: heartbeat, ref: ec68999f-f9d4-41bc-aa55-849efc1f97c0]: {} Socket Push [topic: realtime:users:Lothar, event: phx_join, ref: 0245d09f-3b51-4d9a-93df-0a6d87b9f3e8]: { "config": { "broadcast": { "self": false, "ack": true }, "presence": { "key": "" }, "postgres_changes": [] } } Is connected? True Socket Message Received: {"event":"phx_reply","payload":{"response":{},"status":"ok"},"ref":"ec68999f-f9d4-41bc-aa55-849efc1f97c0","topic":"phoenix"} Socket Message Received: {"event":"phx_reply","payload":{"response":{"postgres_changes":[]},"status":"ok"},"ref":"0245d09f-3b51-4d9a-93df-0a6d87b9f3e8","topic":"realtime:users:Lothar"} Socket Push [topic: realtime:users:Lothar, event: access_token, ref: 71b840bc-4f6d-4d89-aba2-f45d0b7c6926]: { "access_token": "" } Socket Push [topic: realtime:public:users, event: phx_join, ref: df8a4e15-4830-4e91-9675-5b58a70482e6]: { "config": { "broadcast": { "self": false, "ack": false }, "presence": { "key": "" }, "postgres_changes": [ { "schema": "public", "table": "users", "filter": "id=eq.1", "event": "UPDATE" } ] } } Socket Message Received: {"event":"presence_state","payload":{},"ref":null,"topic":"realtime:users:Lothar"} Socket Message Received: {"event":"phx_reply","payload":{"response":{"postgres_changes":[{"id":98019032,"event":"UPDATE","filter":"id=eq.1","schema":"public","table":"users"}]},"status":"ok"},"ref":"df8a4e15-4830-4e91-9675-5b58a70482e6","topic":"realtime:public:users"} Socket Push [topic: realtime:public:users, event: access_token, ref: f779ebe6-fa13-4698-8be9-b9ad676e64be]: { "access_token": "" } Socket Message Received: {"event":"presence_state","payload":{},"ref":null,"topic":"realtime:public:users"} Socket Message Received: {"event":"system","payload":{"channel":"public:users","extension":"postgres_changes","message":"Subscribed to PostgreSQL","status":"ok"},"ref":null,"topic":"realtime:public:users"} Socket Push [topic: realtime:users:grimlothar, event: phx_join, ref: 70d64e2c-0f1e-43ed-a385-7d71651ce676]: { "config": { "broadcast": { "self": false, "ack": true }, "presence": { "key": "" }, "postgres_changes": [] } } Socket Message Received: {"event":"phx_reply","payload":{"response":{"postgres_changes":[]},"status":"ok"},"ref":"70d64e2c-0f1e-43ed-a385-7d71651ce676","topic":"realtime:users:grimlothar"} Socket Push [topic: realtime:users:grimlothar, event: access_token, ref: eb9393a6-dbf7-4553-85fc-267830438a55]: { "access_token": "" } Socket Message Received: {"event":"presence_state","payload":{},"ref":null,"topic":"realtime:users:grimlothar"} Socket Push [topic: realtime:users:grimlothar, event: broadcast, ref: 5862759f-cd8a-4760-bf15-1cde89390464]: { "payload": { "matchRequestFrom": "Lothar", "matchRequestKey": "Lothar-grimlothar" }, "event": "matchRequest" } Socket Message Received: {"event":"phx_reply","payload":{"response":{},"status":"ok"},"ref":"5862759f-cd8a-4760-bf15-1cde89390464","topic":"realtime:users:grimlothar"} Socket Push [topic: phoenix, event: heartbeat, ref: e54140ef-7b04-44a7-9025-4995aefa1cfc]: {} Socket Message Received: {"event":"phx_reply","payload":{"response":{},"status":"ok"},"ref":"e54140ef-7b04-44a7-9025-4995aefa1cfc","topic":"phoenix"} Socket Push [topic: realtime:users:Lothar, event: access_token, ref: 840bcfa0-60f2-4e8d-9e00-5132461bd0c2]: { "access_token": "" } Socket Push [topic: realtime:public:users, event: access_token, ref: 486e0d33-3c4f-4c43-99ef-0d77f774ab24]: { "access_token": "" } Socket Push [topic: realtime:users:grimlothar, event: access_token, ref: 4c4d4632-2f4f-46cf-9040-5a943495d754]: { "access_token": "" } Socket Message Received: {"event":"broadcast","payload":{"event":"matchRequest","payload":{"matchRequestFrom":"grimlothar","matchRequestKey":"Lothar-grimlothar"}},"ref":null,"topic":"realtime:users:Lothar"} Socket Disconnection: Lost UnityEngine.Debug:Log (object) SupabaseManager/<>c:b__8_0 (object,string,System.Exception) (at Assets/SupabaseManager.cs:56) Supabase.Realtime.Debugger:Log (object,string,System.Exception) Supabase.Realtime.RealtimeSocket:HandleSocketDisconnectionHappened (Websocket.Client.DisconnectionInfo) System.Reactive.Subjects.Subject`1:OnNext (Websocket.Client.DisconnectionInfo) Websocket.Client.WebsocketClient/d__81:MoveNext () System.Runtime.CompilerServices.AsyncTaskMethodBuilder:Startd__81> (Websocket.Client.WebsocketClient/d__81&) Websocket.Client.WebsocketClient:Reconnect (Websocket.Client.ReconnectionType,bool,System.Exception) Websocket.Client.WebsocketClient/d__80:MoveNext () System.Runtime.CompilerServices.AsyncTaskMethodBuilder:Startd__80> (Websocket.Client.WebsocketClient/d__80&) Websocket.Client.WebsocketClient:ReconnectSynchronized (Websocket.Client.ReconnectionType,bool,System.Exception) Websocket.Client.WebsocketClient/d__70:MoveNext () System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1:SetResult (int) Mono.Net.Security.MobileAuthenticatedStream/d__57:MoveNext () System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1:SetResult (Mono.Net.Security.AsyncProtocolResult) Mono.Net.Security.AsyncProtocolRequest/d__23:MoveNext () System.Runtime.CompilerServices.AsyncTaskMethodBuilder:SetResult () Mono.Net.Security.AsyncProtocolRequest/d__24:MoveNext () System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1>:SetResult (System.Nullable`1) Mono.Net.Security.AsyncProtocolRequest/d__25:MoveNext () System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1:SetResult (int) Mono.Net.Security.MobileAuthenticatedStream/d__66:MoveNext () System.Threading._ThreadPoolWaitCallback:PerformWaitCallback () Next reconnection attempt will occur at: 9/5/2023 11:17:25 AM UnityEngine.Debug:Log (object) SupabaseManager/<>c:b__8_0 (object,string,System.Exception) (at Assets/SupabaseManager.cs:56) Supabase.Realtime.Debugger:Log (object,string,System.Exception) Supabase.Realtime.RealtimeSocket:HandleSocketError (Websocket.Client.DisconnectionInfo) Supabase.Realtime.RealtimeSocket:HandleSocketDisconnectionHappened (Websocket.Client.DisconnectionInfo) System.Reactive.Subjects.Subject`1:OnNext (Websocket.Client.DisconnectionInfo) Websocket.Client.WebsocketClient/d__81:MoveNext () System.Runtime.CompilerServices.AsyncTaskMethodBuilder:Startd__81> (Websocket.Client.WebsocketClient/d__81&) Websocket.Client.WebsocketClient:Reconnect (Websocket.Client.ReconnectionType,bool,System.Exception) Websocket.Client.WebsocketClient/d__80:MoveNext () System.Runtime.CompilerServices.AsyncTaskMethodBuilder:Startd__80> (Websocket.Client.WebsocketClient/d__80&) Websocket.Client.WebsocketClient:ReconnectSynchronized (Websocket.Client.ReconnectionType,bool,System.Exception) Websocket.Client.WebsocketClient/d__70:MoveNext () System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1:SetResult (int) Mono.Net.Security.MobileAuthenticatedStream/d__57:MoveNext () System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1:SetResult (Mono.Net.Security.AsyncProtocolResult) Mono.Net.Security.AsyncProtocolRequest/d__23:MoveNext () System.Runtime.CompilerServices.AsyncTaskMethodBuilder:SetResult () Mono.Net.Security.AsyncProtocolRequest/d__24:MoveNext () System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1>:SetResult (System.Nullable`1) Mono.Net.Security.AsyncProtocolRequest/d__25:MoveNext () System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1:SetResult (int) Mono.Net.Security.MobileAuthenticatedStream/d__66:MoveNext () System.Threading._ThreadPoolWaitCallback:PerformWaitCallback () Socket Reconnection: Lost UnityEngine.Debug:Log (object) SupabaseManager/<>c:b__8_0 (object,string,System.Exception) (at Assets/SupabaseManager.cs:56) Supabase.Realtime.Debugger:Log (object,string,System.Exception) Supabase.Realtime.RealtimeSocket:HandleSocketReconnectionHappened (Websocket.Client.Models.ReconnectionInfo) System.Reactive.Subjects.Subject`1:OnNext (Websocket.Client.Models.ReconnectionInfo) Websocket.Client.WebsocketClient/d__68:MoveNext () System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1:SetResult (System.Net.WebSockets.WebSocket) Websocket.Client.WebsocketClient/<>c/<<-ctor>b__17_0>d:MoveNext () System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1:SetResult (int) Mono.Net.Security.MobileAuthenticatedStream/d__57:MoveNext () System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1:SetResult (Mono.Net.Security.AsyncProtocolResult) Mono.Net.Security.AsyncProtocolRequest/d__23:MoveNext () System.Runtime.CompilerServices.AsyncTaskMethodBuilder:SetResult () Mono.Net.Security.AsyncProtocolRequest/d__24:MoveNext () System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1>:SetResult (System.Nullable`1) Mono.Net.Security.AsyncProtocolRequest/d__25:MoveNext () System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1:SetResult (int) Mono.Net.Security.MobileAuthenticatedStream/d__66:MoveNext () System.Threading._ThreadPoolWaitCallback:PerformWaitCallback () Socket State Change: Reconnect UnityEngine.Debug:Log (object) SupabaseManager/<>c:b__8_0 (object,string,System.Exception) (at Assets/SupabaseManager.cs:56) Supabase.Realtime.Debugger:Log (object,string,System.Exception) Supabase.Realtime.RealtimeSocket:NotifySocketStateChange (Supabase.Realtime.Constants/SocketState) Supabase.Realtime.RealtimeSocket:HandleSocketOpened () Supabase.Realtime.RealtimeSocket:HandleSocketReconnectionHappened (Websocket.Client.Models.ReconnectionInfo) System.Reactive.Subjects.Subject`1:OnNext (Websocket.Client.Models.ReconnectionInfo) Websocket.Client.WebsocketClient/d__68:MoveNext () System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1:SetResult (System.Net.WebSockets.WebSocket) Websocket.Client.WebsocketClient/<>c/<<-ctor>b__17_0>d:MoveNext () System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1:SetResult (int) Mono.Net.Security.MobileAuthenticatedStream/d__57:MoveNext () System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1:SetResult (Mono.Net.Security.AsyncProtocolResult) Mono.Net.Security.AsyncProtocolRequest/d__23:MoveNext () System.Runtime.CompilerServices.AsyncTaskMethodBuilder:SetResult () Mono.Net.Security.AsyncProtocolRequest/d__24:MoveNext () System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1>:SetResult (System.Nullable`1) Mono.Net.Security.AsyncProtocolRequest/d__25:MoveNext () System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1:SetResult (int) Mono.Net.Security.MobileAuthenticatedStream/d__66:MoveNext () System.Threading._ThreadPoolWaitCallback:PerformWaitCallback () Socket Push [topic: realtime:users:Lothar, event: phx_join, ref: 4b38206a-c416-4d65-9277-1e1cc1ce4e2d]: { "config": { "broadcast": { "self": false, "ack": true }, "presence": { "key": "" }, "postgres_changes": [] } } Socket Push [topic: realtime:public:users, event: phx_join, ref: 6ca43d30-cca7-4a61-809e-804159576c46]: { "config": { "broadcast": { "self": false, "ack": false }, "presence": { "key": "" }, "postgres_changes": [ { "schema": "public", "table": "users", "filter": "id=eq.1", "event": "UPDATE" } ] } } Socket Push [topic: realtime:users:grimlothar, event: phx_join, ref: 9329b77d-15fd-43bd-8bd8-1e01caa85967]: { "config": { "broadcast": { "self": false, "ack": true }, "presence": { "key": "" }, "postgres_changes": [] } } Socket Push [topic: phoenix, event: heartbeat, ref: 86d0585b-e7c6-462d-b50a-f9d2707c018b]: {} Channel not joined. Try to rejoin? ```
acupofjose commented 1 year ago

Okay! Late to the party. But if this is still an issue, I'd wonder if the socket is failing because of an expired token. Ideally the supabase client would handle your authentication and token renewal. So in your startup code, you'd just log in the user using the Supabase.Auth (gotrue) client.

So adjust your Start() method to something like the following:

async void Start()
{
        Debug.Log("SupabaseManager start");
        string url = SupabaseURL;
        string key = SupabasePublicKey;
        string userAccessToken = ApiClient.Get().AccessToken
        string userRefreshToken = ApiClient.Get().RefreshToken // <---- This is important

         _supabase = new Client(url, key);
         await _supabase.InitializeAsync();

        // Set session and start token refresh loop
        await _supabase.Auth.SetSession(userAccessToken, userRefreshToken);

        _supabase.Realtime.AddDebugHandler((sender, message, exception) => Debug.Log(message));
        _supabase.Realtime.AddStateChangedHandler(SocketEventHandler);

        var realtimeClient = await _supabase.Realtime.ConnectAsync();

        Debug.Log("Is connected? " + realtimeClient.Socket?.IsConnected);
}
acupofjose commented 1 year ago

Actually, please try again on 0.13.3 and see if that fixes it for you!

GrimLothar commented 12 months ago

Hey @acupofjose what would be the best way to update the library? I used to have an issue with making the library work (like mentioned here) unless I cloned the repo from here. But that repo doesn't seem to be updated with 0.13.3.

Thank you

acupofjose commented 11 months ago

I think you're looking for the wiki entry here! Unfortunately, I don't have write access to that repo, so I'm not much help there.

wiverson commented 11 months ago

FYI updated the template to latest as of 9/27/2023. Added @acupofjose as a contributor to the template in case I'm, I dunno, retire off to some beach with no Internet or something. ;)

GrimLothar commented 11 months ago

FYI updated the template to latest as of 9/27/2023. Added @acupofjose as a contributor to the template in case I'm, I dunno, retire off to some beach with no Internet or something. ;)

Hey, thanks for the quick turnaround! Any chance you can give a quick rundown of HOW you update the template to newer versions? So I don't have to wait/bug you in future updates

Thanks!

wiverson commented 11 months ago

Right now I use the command line nuget to fetch the entire dependency graph that's in the wiki page on Unity. Then I manually remove a bunch of libraries (eg a bunch of the System and Newtonsoft Json) by hand that cause conflicts with Unity specific versions.

It's a giant PITA TBH but I haven't needed to do it enough yet to do the Right Thing(tm) and make a GitHub Action to create a Unity package manager friendly version... yet. ;)