Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 28 additions & 2 deletions Realtime/Broadcast/BroadcastOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
namespace Supabase.Realtime.Broadcast;

/// <summary>
/// Options
/// Options
/// </summary>
public class BroadcastOptions
{
Expand All @@ -19,6 +19,32 @@ public class BroadcastOptions
[JsonProperty("ack")]
public bool BroadcastAck { get; set; } = false;

/// <summary>
/// replay option instructs server to replay broadcast messages
/// </summary>
[JsonProperty("replay", NullValueHandling = NullValueHandling.Ignore)]
public ReplayOptions? Replay { get; set; }

/// <summary>
/// Options for replaying events in broadcast configurations.
/// </summary>
public class ReplayOptions
{
/// <summary>
/// Specifies the starting point in time, in milliseconds since the Unix epoch,
/// from which events should be replayed in the broadcast configuration.
/// </summary>
[JsonProperty("since")]
public long Since { get; set; }

/// <summary>
/// Specifies the maximum number of events to be replayed during broadcast.
/// When set to null, there is no limit to the number of events replayed.
/// </summary>
[JsonProperty("limit", NullValueHandling = NullValueHandling.Ignore)]
public int? Limit { get; set; }
}

/// <summary>
/// Initializes broadcast options
/// </summary>
Expand All @@ -29,4 +55,4 @@ public BroadcastOptions(bool broadcastSelf = false, bool broadcastAck = false)
BroadcastSelf = broadcastSelf;
BroadcastAck = broadcastAck;
}
}
}
52 changes: 44 additions & 8 deletions Realtime/Channel/ChannelOptions.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
using Newtonsoft.Json;
using System;
using System;
using System.Collections.Generic;
using Newtonsoft.Json;

namespace Supabase.Realtime.Channel;

/// <summary>
/// Channel Options
/// Represents configuration options for a Realtime channel.
/// </summary>
/// <remarks>
/// This class contains all the necessary configuration options for establishing and maintaining
/// a Realtime channel connection, including authentication, parameters, and serialization settings.
/// </remarks>
public class ChannelOptions
{
/// <summary>
Expand All @@ -29,16 +33,48 @@ public class ChannelOptions
/// </summary>
public JsonSerializerSettings SerializerSettings { get; }

/// <summary>
/// Gets a value indicating whether the channel is private.
/// </summary>
/// <value>
/// <c>true</c> if the channel is private; otherwise, <c>false</c>.
/// </value>
public bool IsPrivate { get; } = false;

/// <summary>
/// The Channel Options (typically only called from within the <see cref="Client"/>)
/// </summary>
/// <param name="clientOptions">The client configuration options.</param>
/// <param name="retrieveAccessToken">A function that returns the current access token.</param>
/// <param name="serializerSettings">The JSON serializer settings to be used for message serialization.</param>
public ChannelOptions(
ClientOptions clientOptions,
Func<string?> retrieveAccessToken,
JsonSerializerSettings serializerSettings
)
{
ClientOptions = clientOptions;
SerializerSettings = serializerSettings;
RetrieveAccessToken = retrieveAccessToken;
}

/// <summary>
/// The Channel Options (typically only called from within the <see cref="Client"/>)
/// </summary>
/// <param name="clientOptions"></param>
/// <param name="retrieveAccessToken"></param>
/// <param name="serializerSettings"></param>
public ChannelOptions(ClientOptions clientOptions, Func<string?> retrieveAccessToken, JsonSerializerSettings serializerSettings)
/// <param name="clientOptions">The client configuration options.</param>
/// <param name="retrieveAccessToken">A function that returns the current access token.</param>
/// <param name="serializerSettings">The JSON serializer settings to be used for message serialization.</param>
/// <param name="isPrivate">A value indicating whether the channel is private.</param>
public ChannelOptions(
ClientOptions clientOptions,
Func<string?> retrieveAccessToken,
JsonSerializerSettings serializerSettings,
bool isPrivate
)
{
ClientOptions = clientOptions;
SerializerSettings = serializerSettings;
RetrieveAccessToken = retrieveAccessToken;
IsPrivate = isPrivate;
}
}
}
8 changes: 6 additions & 2 deletions Realtime/Channel/JoinPush.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@ internal class JoinPush
[JsonProperty("config")]
public JoinPushConfig Config { get; private set; }

public JoinPush(BroadcastOptions? broadcastOptions = null, PresenceOptions? presenceOptions = null, List<PostgresChangesOptions>? postgresChangesOptions = null)
public JoinPush(BroadcastOptions? broadcastOptions = null, PresenceOptions? presenceOptions = null, List<PostgresChangesOptions>? postgresChangesOptions = null, bool? isPrivate = null)
{
Config = new JoinPushConfig
{
Broadcast = broadcastOptions,
Presence = presenceOptions,
PostgresChanges = postgresChangesOptions ?? new List<PostgresChangesOptions>()
PostgresChanges = postgresChangesOptions ?? new List<PostgresChangesOptions>(),
IsPrivate = isPrivate
};
}

Expand All @@ -31,5 +32,8 @@ internal class JoinPushConfig

[JsonProperty("postgres_changes", NullValueHandling = NullValueHandling.Ignore)]
public List<PostgresChangesOptions> PostgresChanges { get; set; } = new List<PostgresChangesOptions> { };

[JsonProperty("private", NullValueHandling = NullValueHandling.Ignore)]
public bool? IsPrivate { get; set; }
}
}
23 changes: 23 additions & 0 deletions Realtime/Client.cs
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,29 @@ public RealtimeChannel Channel(string channelName)
return subscription;
}

/// <summary>
/// Adds a RealtimeChannel subscription with custom options - if a subscription exists with the same signature, the existing subscription will be returned.
/// </summary>
/// <param name="channelName">The name of the Channel to join</param>
/// <param name="options">Custom channel options for configuring the subscription</param>
/// <returns>A RealtimeChannel instance representing the subscription</returns>
/// <exception cref="Exception">Thrown when Socket is null, indicating Connect() was not called</exception>
public RealtimeChannel Channel(string channelName, ChannelOptions options)
{
var topic = $"realtime:{channelName}";

if (_subscriptions.TryGetValue(topic, out var channel))
return channel;

if (Socket == null)
throw new Exception("Socket must exist, was `Connect` called?");

var subs = new RealtimeChannel(Socket!, topic, options);
_subscriptions.Add(topic, subs);

return subs;
}

/// <summary>
/// Adds a RealtimeChannel subscription - if a subscription exists with the same signature, the existing subscription will be returned.
/// </summary>
Expand Down
10 changes: 10 additions & 0 deletions Realtime/Interfaces/IRealtimeChannel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,16 @@ public interface IRealtimeChannel
RealtimeBroadcast<TBroadcastResponse> Register<TBroadcastResponse>(bool broadcastSelf = false,
bool broadcastAck = false) where TBroadcastResponse : BaseBroadcast;


/// <summary>
/// Registers the channel with the specified configuration options.
/// </summary>
/// <param name="options">The configuration options for the broadcast registration.</param>
/// <typeparam name="TBroadcastResponse">The type of the broadcast response, which must inherit from <see cref="BaseBroadcast"/>.</typeparam>
/// <returns>A <see cref="RealtimeBroadcast{TBroadcastResponse}"/> instance for managing the broadcast.</returns>
public RealtimeBroadcast<TBroadcastResponse> Register<TBroadcastResponse>(BroadcastOptions options)
where TBroadcastResponse : BaseBroadcast;

/// <summary>
/// Register presence options, must be called to use <see cref="IRealtimePresence"/>, and prior to <see cref="Subscribe"/>
/// </summary>
Expand Down
10 changes: 10 additions & 0 deletions Realtime/Interfaces/IRealtimeClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Net.WebSockets;
using System.Threading.Tasks;
using Supabase.Core.Interfaces;
using Supabase.Realtime.Channel;
using Supabase.Realtime.Exceptions;
using static Supabase.Realtime.Constants;

Expand Down Expand Up @@ -85,6 +86,15 @@ public interface IRealtimeClient<TSocket, TChannel>: IGettableHeaders
/// <returns></returns>
TChannel Channel(string channelName);

/// <summary>
/// Adds a RealtimeChannel subscription with custom options - if a subscription exists with the same signature, the existing subscription will be returned.
/// </summary>
/// <param name="channelName">The name of the Channel to join</param>
/// <param name="options">Custom channel options for configuring the subscription</param>
/// <returns>A RealtimeChannel instance representing the subscription</returns>
/// <exception cref="Exception">Thrown when Socket is null, indicating Connect() was not called</exception>
TChannel Channel(string channelName, ChannelOptions options);

/// <summary>
/// Shorthand initialization of a channel with postgres_changes options already set.
/// </summary>
Expand Down
16 changes: 16 additions & 0 deletions Realtime/Models/BaseBroadcast.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,20 @@
/// </summary>
[JsonProperty("payload")]
public Dictionary<string, object>? Payload { get; set; }

/// <summary>
/// Additional metadata associated with a broadcast event.
/// </summary>
[JsonProperty("meta", NullValueHandling = NullValueHandling.Ignore)]
public Meta? Meta { get; set; }
}


public class Meta
{
[JsonProperty("id")]
public string Id { get; set; }

Check warning on line 47 in Realtime/Models/BaseBroadcast.cs

View workflow job for this annotation

GitHub Actions / build-and-test

Non-nullable property 'Id' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 47 in Realtime/Models/BaseBroadcast.cs

View workflow job for this annotation

GitHub Actions / build-and-test

Non-nullable property 'Id' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

[JsonProperty("replayed")]
public bool Replayed { get; set; }
}
29 changes: 28 additions & 1 deletion Realtime/RealtimeChannel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,33 @@ public RealtimeBroadcast<TBroadcastResponse> Register<TBroadcastResponse>(bool b
return instance;
}

/// <summary>
/// Registers the channel for broadcast with the specified options.
/// </summary>
/// <typeparam name="TBroadcastResponse">The type of the broadcast response, which must inherit from <see cref="BaseBroadcast"/>.</typeparam>
/// <param name="options">The broadcast options to configure the channel's broadcast behavior.</param>
/// <returns>Returns an instance of <see cref="RealtimeBroadcast{TBroadcastResponse}"/> initialized with the specified broadcast options.</returns>
/// <exception cref="InvalidOperationException">Thrown if the method is called multiple times for the same channel.</exception>
public RealtimeBroadcast<TBroadcastResponse> Register<TBroadcastResponse>(BroadcastOptions options) where TBroadcastResponse : BaseBroadcast
{
if (_broadcast != null)
throw new InvalidOperationException(
"Register can only be called with broadcast options for a channel once.");

if (!Options.IsPrivate && options.Replay != null)
throw new InvalidOperationException($"tried to use replay on public channel '{Topic}'. It must be a private channel.");

BroadcastOptions = options;

var instance =
new RealtimeBroadcast<TBroadcastResponse>(this, BroadcastOptions, Options.SerializerSettings);
_broadcast = instance;

BroadcastHandler = (_, response) => _broadcast.TriggerReceived(response);

return instance;
}

/// <summary>
/// Registers a <see cref="RealtimePresence{TPresenceResponse}"/> instance - allowing presence responses to be parsed and state to be tracked.
/// </summary>
Expand Down Expand Up @@ -592,7 +619,7 @@ internal void Enqueue(Push push)
/// </summary>
/// <returns></returns>
private Push GenerateJoinPush() => new(Socket, this, ChannelEventJoin,
payload: new JoinPush(BroadcastOptions, PresenceOptions, PostgresChangesOptions));
payload: new JoinPush(BroadcastOptions, PresenceOptions, PostgresChangesOptions, Options.IsPrivate));

/// <summary>
/// Generates an auth push.
Expand Down
Loading
Loading