Skip to content

Commit b81fc39

Browse files
committed
[Re:#167](supabase-community/supabase-csharp#167) Adds support for specifying GetHeaders on the RealtimeClient which are included on the initial request to the server to establish websocket connection.
1 parent 04e1821 commit b81fc39

File tree

7 files changed

+65
-10
lines changed

7 files changed

+65
-10
lines changed

Realtime/Client.cs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,21 @@ public class Client : IRealtimeClient<RealtimeSocket, RealtimeChannel>
4646
/// </summary>
4747
public ClientOptions Options { get; }
4848

49+
private Func<Dictionary<string, string>>? _getHeaders { get; set; }
50+
51+
/// <inheritdoc />
52+
public Func<Dictionary<string, string>>? GetHeaders
53+
{
54+
get => _getHeaders;
55+
set
56+
{
57+
_getHeaders = value;
58+
59+
if (Socket != null)
60+
Socket.GetHeaders = value;
61+
}
62+
}
63+
4964
/// <summary>
5065
/// Custom Serializer resolvers and converters that will be used for encoding and decoding Postgrest JSON responses.
5166
///
@@ -124,6 +139,7 @@ public async Task<IRealtimeClient<RealtimeSocket, RealtimeChannel>> ConnectAsync
124139
}
125140

126141
Socket = new RealtimeSocket(_realtimeUrl, Options);
142+
Socket.GetHeaders = GetHeaders;
127143

128144
IRealtimeSocket.StateEventHandler? socketStateHandler = null;
129145
socketStateHandler = (sender, state) =>
@@ -172,7 +188,7 @@ public IRealtimeClient<RealtimeSocket, RealtimeChannel> Connect(
172188
callback?.Invoke(this, null);
173189
return this;
174190
}
175-
191+
176192
Socket = new RealtimeSocket(_realtimeUrl, Options);
177193
IRealtimeSocket.StateEventHandler? socketStateHandler = null;
178194
IRealtimeSocket.ErrorEventHandler? errorEventHandler = null;
@@ -183,12 +199,12 @@ public IRealtimeClient<RealtimeSocket, RealtimeChannel> Connect(
183199

184200
Socket.AddMessageReceivedHandler(HandleSocketMessageReceived);
185201
Socket.AddHeartbeatHandler(HandleSocketHeartbeat);
186-
202+
187203
sender.RemoveStateChangedHandler(socketStateHandler!);
188204
sender.RemoveErrorHandler(errorEventHandler!);
189-
205+
190206
NotifySocketStateChange(SocketState.Open);
191-
207+
192208
callback?.Invoke(this, null);
193209
};
194210

Realtime/ClientOptions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public class ClientOptions
5454
/// <summary>
5555
/// Request headers to be appended to the connection string.
5656
/// </summary>
57-
public readonly Dictionary<string, object> Headers = new();
57+
public readonly Dictionary<string, string> Headers = new();
5858

5959
/// <summary>
6060
/// The optional params to pass when connecting

Realtime/Interfaces/IRealtimeClient.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Collections.ObjectModel;
55
using System.Net.WebSockets;
66
using System.Threading.Tasks;
7+
using Supabase.Core.Interfaces;
78
using Supabase.Realtime.Exceptions;
89
using static Supabase.Realtime.Constants;
910

@@ -14,7 +15,7 @@ namespace Supabase.Realtime.Interfaces;
1415
/// </summary>
1516
/// <typeparam name="TSocket"></typeparam>
1617
/// <typeparam name="TChannel"></typeparam>
17-
public interface IRealtimeClient<TSocket, TChannel>
18+
public interface IRealtimeClient<TSocket, TChannel>: IGettableHeaders
1819
where TSocket : IRealtimeSocket
1920
where TChannel : IRealtimeChannel
2021
{

Realtime/Interfaces/IRealtimeSocket.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System;
33
using System.Net.WebSockets;
44
using System.Threading.Tasks;
5+
using Supabase.Core.Interfaces;
56
using Supabase.Realtime.Exceptions;
67
using static Supabase.Realtime.Constants;
78

@@ -10,7 +11,7 @@ namespace Supabase.Realtime.Interfaces;
1011
/// <summary>
1112
/// Contract for a realtime socket.
1213
/// </summary>
13-
public interface IRealtimeSocket
14+
public interface IRealtimeSocket: IGettableHeaders
1415
{
1516
/// <summary>
1617
/// Is this socket connected?

Realtime/Realtime.csproj

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,14 @@
3737
<Version Condition=" '$(Version)' == '' ">$(VersionPrefix)</Version>
3838
</PropertyGroup>
3939

40+
<ItemGroup>
41+
<InternalsVisibleTo Include="RealtimeTests" />
42+
</ItemGroup>
43+
4044
<ItemGroup>
4145
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
4246
<PackageReference Include="Supabase.Core" Version="1.0.0" />
43-
<PackageReference Include="Supabase.Postgrest" Version="4.0.0" />
47+
<PackageReference Include="Supabase.Postgrest" Version="4.0.3" />
4448
</ItemGroup>
4549

4650
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">

Realtime/RealtimeSocket.cs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Threading;
66
using System.Threading.Tasks;
77
using Newtonsoft.Json;
8+
using Supabase.Core.Extensions;
89
using Supabase.Realtime.Socket;
910
using Supabase.Realtime.Exceptions;
1011
using Supabase.Realtime.Interfaces;
@@ -15,7 +16,6 @@
1516
#endif
1617

1718
namespace Supabase.Realtime;
18-
1919
/// <summary>
2020
/// Socket connection handler.
2121
/// </summary>
@@ -44,6 +44,15 @@ private string EndpointUrl
4444
}
4545
}
4646

47+
/// <inheritdoc />
48+
public Func<Dictionary<string, string>>? GetHeaders { get; set; }
49+
50+
/// <summary>
51+
/// Shortcut property that merges <see cref="GetHeaders"/> with <see cref="_options"/>
52+
/// Headers specified in <see cref="_options"/> take precedence over <see cref="GetHeaders"/>
53+
/// </summary>
54+
internal Dictionary<string, string> Headers => GetHeaders != null ? GetHeaders().MergeLeft(_options.Headers) : _options.Headers;
55+
4756
private readonly List<IRealtimeSocket.StateEventHandler> _socketEventHandlers = new();
4857
private readonly List<IRealtimeSocket.MessageEventHandler> _messageEventHandlers = new();
4958
private readonly List<IRealtimeSocket.HeartbeatEventHandler> _heartbeatEventHandlers = new();
@@ -76,7 +85,15 @@ public RealtimeSocket(string endpoint, ClientOptions options)
7685
if (!options.Headers.ContainsKey("X-Client-Info"))
7786
options.Headers.Add("X-Client-Info", Core.Util.GetAssemblyVersion(typeof(Client)));
7887

79-
_connection = new WebsocketClient(new Uri(EndpointUrl));
88+
_connection = new WebsocketClient(new Uri(EndpointUrl), () =>
89+
{
90+
var socket = new ClientWebSocket();
91+
92+
foreach (var header in Headers)
93+
socket.Options.SetRequestHeader(header.Key, header.Value);
94+
95+
return socket;
96+
});
8097
}
8198

8299
void IDisposable.Dispose()

RealtimeTests/ClientTests.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.Diagnostics;
34
using System.Net;
5+
using System.Net.Sockets;
46
using System.Threading.Tasks;
57
using Microsoft.VisualStudio.TestTools.UnitTesting;
68
using Supabase.Realtime.Exceptions;
@@ -130,4 +132,18 @@ public async Task ClientCanReconnectAfterProgrammaticDisconnect()
130132
client!.Disconnect();
131133
await client!.ConnectAsync();
132134
}
135+
136+
[TestMethod("Client: Sets headers")]
137+
public async Task ClientCanSetHeaders()
138+
{
139+
client!.Disconnect();
140+
141+
client!.GetHeaders = () => new Dictionary<string, string>() { { "testing", "123" } };
142+
await client.ConnectAsync();
143+
144+
Assert.IsNotNull(client!);
145+
Assert.IsNotNull(client!.Socket);
146+
Assert.IsNotNull(client!.Socket.GetHeaders);
147+
Assert.AreEqual("123",client.Socket.GetHeaders()["testing"]);
148+
}
133149
}

0 commit comments

Comments
 (0)