realtime-csharp
6.0.4
Supabase is migrating its packages to match the Nuget naming conventions.
dotnet add package realtime-csharp --version 6.0.4
NuGet\Install-Package realtime-csharp -Version 6.0.4
<PackageReference Include="realtime-csharp" Version="6.0.4" />
paket add realtime-csharp --version 6.0.4
#r "nuget: realtime-csharp, 6.0.4"
// Install realtime-csharp as a Cake Addin #addin nuget:?package=realtime-csharp&version=6.0.4 // Install realtime-csharp as a Cake Tool #tool nuget:?package=realtime-csharp&version=6.0.4
<p align="center"> <img width="300" src=".github/logo.png"/> </p> <p align="center"> <img src="https://github.com/supabase/realtime-csharp/workflows/Build%20And%20Test/badge.svg"/> <a href="https://www.nuget.org/packages/realtime-csharp/"> <img src="https://img.shields.io/badge/dynamic/json?color=green&label=Nuget%20Release&query=data[0].version&url=https%3A%2F%2Fazuresearch-usnc.nuget.org%2Fquery%3Fq%3Dpackageid%3Arealtime-csharp"/> </a> </p>
BREAKING CHANGES MOVING FROM v5.x.x to v6.x.x
- The realtime client now takes a "fail-fast" approach. On establishing an initial connection, client will throw
a
RealtimeException
inConnectAsync()
if the socket server is unreachable. After an initial connection has been established, the client will continue attempting reconnections indefinitely until disconnected. - [Major, New] C#
EventHandlers
have been changed todelegates
. This should allow for cleaner event data access over the previous subclassedEventArgs
setup. Events are scoped accordingly. For example, theRealtimeSocket
error handlers will receive events regarding socket connectivity; whereas theRealtimeChannel
error handlers will receive events according toChannel
joining/leaving/etc. This is implemented with the following methods prefixed by ( Add/Remove/Clear):RealtimeBroadcast.AddBroadcastEventHandler
RealtimePresence.AddPresenceEventHandler
RealtimeSocket.AddStateChangedHandler
RealtimeSocket.AddMessageReceivedHandler
RealtimeSocket.AddHeartbeatHandler
RealtimeSocket.AddErrorHandler
RealtimeClient.AddDebugHandler
RealtimeClient.AddStateChangedHandler
RealtimeChannel.AddPostgresChangeHandler
RealtimeChannel.AddMessageReceivedHandler
RealtimeChannel.AddErrorHandler
Push.AddMessageReceivedHandler
- [Major, new]
ClientOptions.Logger
has been removed in favor ofClient.AddDebugHandler()
which allows for implementing custom logging solutions if desired.- A simple logger can be set up with the following:
client.AddDebugHandler((sender, message, exception) => Debug.WriteLine(message));
- [Major]
Connect()
has been markedObsolete
in favor ofConnectAsync()
- Custom reconnection logic has been removed in favor of using the built-in logic from
Websocket.Client@4.6.1
. - Exceptions that are handled within this library have been marked as
RealtimeException
s. - The local, docker-composed test suite has been brought back (as opposed to remotely testing on live supabase servers) to test against.
- Comments have been added throughout the entire codebase and an
XML
file is now generated on build.
See realtime-csharp in action here.
realtime-csharp
is written as a client library for supabase/realtime.
Documentation can be found here.
The bulk of this library is a translation and c-sharp-ification of the supabase/realtime-js library.
The Websocket-sharp implementation that Realtime-csharp is dependent on does not support TLS1.3
Getting Started
Care was had to make this API as easy<sup>tm</sup> to interact with as possible. Connect()
and Subscribe()
have await
-able signatures
which allow Users to be assured that a connection exists prior to interacting with it.
var endpoint = "ws://realtime-dev.localhost:4000/socket";
client = new Client(endpoint);
await client.ConnectAsync();
// Shorthand for registering a postgres_changes subscription
var channel = client.Channel("realtime", "public", "todos");
// Listen to Updates
channel.AddPostgresChangeHandler(ListenType.Updates, (_, change) =>
{
var model = change.Model<Todo>();
var oldModel = change.OldModel<Todo>();
});
await channel.Subscribe();
Leveraging Postgrest.BaseModel
s, one ought to be able to coerce SocketResponse Records into their associated models by
calling:
// ...
var channel = client.Channel("realtime", "public", "users");
channel.AddPostgresChangeHandler(ListenType.Inserts, (_, change) =>
{
var model = change.Model<Todo>();
});
await channel.Subscribe();
Broadcast
"Broadcast follows the publish-subscribe pattern where a client publishes messages to a channel with a unique identifier. For example, a user could send a message to a channel with id room-1.
Other clients can elect to receive the message in real-time by subscribing to the channel with id room-1. If these clients are online and subscribed then they will receive the message.
Broadcast works by connecting your client to the nearest Realtime server, which will communicate with other servers to relay messages to other clients.
A common use-case is sharing a user's cursor position with other clients in an online game."
Given the following model (CursorBroadcast
):
class MouseBroadcast : BaseBroadcast<MouseStatus> { }
class MouseStatus
{
[JsonProperty("mouseX")]
public float MouseX { get; set; }
[JsonProperty("mouseY")]
public float MouseY { get; set; }
[JsonProperty("userId")]
public string UserId { get; set; }
}
Listen for typed broadcast events:
var channel = supabase.Realtime.Channel("cursor");
var broadcast = channel.Register<MouseBroadcast>(false, true);
broadcast.AddBroadcastEventHandler((sender, _) =>
{
// Retrieved typed model.
var state = broadcast.Current();
Debug.WriteLine($"{state.Payload}: {state.Payload.MouseX}:{state.Payload.MouseY}");
});
await channel.Subscribe();
Broadcast an event:
var channel = supabase.Realtime.Channel("cursor");
var data = new CursorBroadcast { Event = "cursor", Payload = new MouseStatus { MouseX = 123, MouseY = 456 } };
channel.Send(ChannelType.Broadcast, data);
Presence
"Presence utilizes an in-memory conflict-free replicated data type (CRDT) to track and synchronize shared state in an eventually consistent manner. It computes the difference between existing state and new state changes and sends the necessary updates to clients via Broadcast.
When a new client subscribes to a channel, it will immediately receive the channel's latest state in a single message instead of waiting for all other clients to send their individual states.
Clients are free to come-and-go as they please, and as long as they are all subscribed to the same channel then they will all have the same Presence state as each other.
The neat thing about Presence is that if a client is suddenly disconnected (for example, they go offline), their state will be automatically removed from the shared state. If you've ever tried to build an “I'm online” feature which handles unexpected disconnects, you'll appreciate how useful this is."
Given the following model: (UserPresence
)
class UserPresence: BasePresence
{
[JsonProperty("lastSeen")]
public DateTime LastSeen { get; set; }
}
Listen for typed presence events:
var presenceId = Guid.NewGuid().ToString();
var channel = supabase.Realtime.Channel("last-seen");
var presence = channel.Register<UserPresence>(presenceId);
presence.AddPresenceEventHandler(EventType.Sync, (sender, type) =>
{
foreach (var state in presence.CurrentState)
{
var userId = state.Key;
var lastSeen = state.Value.First().LastSeen;
Debug.WriteLine($"{userId}: {lastSeen}");
}
});
await channel.Subscribe();
Track a user presence event:
var presenceId = Guid.NewGuid().ToString();
var channel = supabase.Realtime.Channel("last-seen");
var presence = channel.Register<UserPresence>(presenceId);
presence.Track(new UserPresence { LastSeen = DateTime.Now });
Postgres Changes
"Postgres Changes enable you to listen to database changes and have them broadcast to authorized clients based on Row Level Security (RLS) policies.
This works by Realtime polling your database's logical replication slot for changes, passing those changes to the apply_rls SQL function to determine which clients have permission, and then using Broadcast to send those changes to clients.
Realtime requires a publication called supabase_realtime
to determine which tables to poll. You must add tables to
this publication prior to clients subscribing to channels that want to listen for database changes.
We strongly encourage you to enable RLS on your database tables and have RLS policies in place to prevent unauthorized parties from accessing your data."
Using the new Register
method:
var channel = supabase.Realtime.Channel("public-users");
channel.Register(new PostgresChangesOptions("public", "users"));
channel.AddPostgresChangeHandler(ListenType.All, (sender, change) =>
{
switch (change.Event)
{
case EventType.Insert:
// User has been created
break;
case EventType.Update:
// User has been updated
break;
case EventType.Delete:
// User has been deleted
break;
}
});
await channel.Subscribe();
Status
- Client Connects to Websocket
- Socket Event Handlers
- Open
- Close - when channel is explicitly closed by server or by calling
Channel.Unsubscribe()
- Error
- Realtime Event Handlers
-
INSERT
-
UPDATE
-
DELETE
-
*
-
- Join channels of format:
-
{database}
-
{database}:{schema}
-
{database}:{schema}:{table}
-
{database}:{schema}:{table}:{col}.eq.{val}
-
- Responses supply a Generically Typed Model derived from
BaseModel
- Ability to remove subscription to Realtime Events
- Ability to disconnect from socket.
- Socket reconnects when possible
- Unit Tests
- Documentation
- Nuget Release
Package made possible through the efforts of:
Join the ranks! See a problem? Help fix it!
<a href="https://github.com/supabase-community/realtime-csharp/graphs/contributors"> <img src="https://contrib.rocks/image?repo=supabase-community/realtime-csharp" /> </a>
Made with contrib.rocks.
Contributing
We are more than happy to have contributions! Please submit a PR.
Testing
Note that the latest versions of supabase/realtime
expect to be able to access a subdomain matching the tenant. For
the case of testing, this means that realtime-dev.localhost:4000
should be available. To have tests run locally,
please add a hosts entry on your system for: 127.0.0.1 realtime-dev.localhost
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
.NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
.NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
.NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
MonoAndroid | monoandroid was computed. |
MonoMac | monomac was computed. |
MonoTouch | monotouch was computed. |
Tizen | tizen40 was computed. tizen60 was computed. |
Xamarin.iOS | xamarinios was computed. |
Xamarin.Mac | xamarinmac was computed. |
Xamarin.TVOS | xamarintvos was computed. |
Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- Newtonsoft.Json (>= 13.0.3)
- postgrest-csharp (>= 3.2.2)
- supabase-core (>= 0.0.3)
- Websocket.Client (>= 4.6.1)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on realtime-csharp:
Package | Downloads |
---|---|
supabase-csharp
A C# implementation of the Supabase client |
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated | |
---|---|---|---|
6.0.4 | 44,272 | 7/15/2023 | |
6.0.3 | 4,471 | 6/10/2023 | |
6.0.2 | 167 | 6/10/2023 | |
6.0.1 | 1,870 | 5/23/2023 | |
6.0.0 | 149 | 5/22/2023 | |
5.0.5 | 4,054 | 4/28/2023 | |
5.0.4 | 3,538 | 3/24/2023 | |
5.0.3 | 1,723 | 3/9/2023 | |
5.0.2 | 795 | 3/3/2023 | |
5.0.1 | 5,415 | 2/7/2023 | |
5.0.0 | 1,611 | 1/31/2023 | |
4.0.1 | 16,223 | 11/9/2022 | |
4.0.0 | 378 | 11/9/2022 | |
3.0.1 | 8,795 | 5/28/2022 | |
3.0.0 | 9,779 | 2/19/2022 | |
2.0.8 | 2,319 | 12/30/2021 | |
2.0.7 | 675 | 12/25/2021 | |
2.0.6 | 1,358 | 11/30/2021 | |
2.0.5 | 1,160 | 11/29/2021 | |
2.0.4 | 809 | 10/29/2021 | |
2.0.3 | 584 | 10/26/2021 | |
2.0.2 | 394 | 10/23/2021 | |
2.0.1 | 413 | 10/23/2021 | |
2.0.0 | 1,814 | 9/15/2021 | |
1.0.6 | 2,204 | 8/25/2021 | |
1.0.5 | 315 | 8/24/2021 | |
1.0.4 | 1,649 | 2/17/2021 | |
1.0.3 | 519 | 2/6/2021 | |
1.0.2 | 357 | 2/3/2021 | |
1.0.1 | 352 | 2/1/2021 | |
1.0.0 | 373 | 1/31/2021 |