diff --git a/Assets/Plugins/StreamChat/Core/Exceptions/StreamApiException.cs b/Assets/Plugins/StreamChat/Core/Exceptions/StreamApiException.cs
index 91442603..75f42418 100644
--- a/Assets/Plugins/StreamChat/Core/Exceptions/StreamApiException.cs
+++ b/Assets/Plugins/StreamChat/Core/Exceptions/StreamApiException.cs
@@ -6,13 +6,69 @@
namespace StreamChat.Core.Exceptions
{
///
- /// Exception thrown when API request failed
+ /// Exception thrown when an API request is rejected by the server. Inspect
+ /// (HTTP status) and (Stream's numeric error code) to react to a specific failure.
+ /// The most common branches are exposed as helpers
+ /// (e.g. ).
+ /// See API Error Codes for the full list.
///
public class StreamApiException : Exception
{
+ // HTTP 400 — Bad Request
+ public const int InputErrorHttpStatusCode = 400;
+ public const int InputErrorStreamCode = 4;
+
+ public const int MessageTooLongErrorHttpStatusCode = 400;
+ public const int MessageTooLongErrorStreamCode = 20;
+
+ public const int MessageModerationFailedErrorHttpStatusCode = 400;
+ public const int MessageModerationFailedErrorStreamCode = 73;
+
+ // HTTP 401 — Unauthorized
+ public const int AuthenticationErrorHttpStatusCode = 401;
+ public const int AuthenticationErrorStreamCode = 5;
+
+ public const int TokenExpiredErrorHttpStatusCode = 401;
+ public const int TokenExpiredErrorStreamCode = 40;
+
+ public const int TokenNotValidYetErrorHttpStatusCode = 401;
+ public const int TokenNotValidYetErrorStreamCode = 41;
+
+ public const int TokenBeforeIssuedAtErrorHttpStatusCode = 401;
+ public const int TokenBeforeIssuedAtErrorStreamCode = 42;
+
+ public const int TokenSignatureInvalidErrorHttpStatusCode = 401;
+ public const int TokenSignatureInvalidErrorStreamCode = 43;
+
+ // HTTP 403 — Forbidden
+ public const int PermissionDeniedErrorHttpStatusCode = 403;
+ public const int PermissionDeniedErrorStreamCode = 17;
+
+ public const int CooldownErrorHttpStatusCode = 403;
+ public const int CooldownErrorStreamCode = 60;
+
+ public const int NoAccessToChannelsErrorHttpStatusCode = 403;
+ public const int NoAccessToChannelsErrorStreamCode = 70;
+
+ public const int AppSuspendedErrorHttpStatusCode = 403;
+ public const int AppSuspendedErrorStreamCode = 99;
+
+ // HTTP 404 — Not Found
+ public const int DoesNotExistErrorHttpStatusCode = 404;
+ public const int DoesNotExistErrorStreamCode = 16;
+
+ // HTTP 413 — Payload Too Large
+ public const int PayloadTooBigErrorHttpStatusCode = 413;
+ public const int PayloadTooBigErrorStreamCode = 22;
+
+ // HTTP 429 — Too Many Requests
public const int RateLimitErrorHttpStatusCode = 429;
public const int RateLimitErrorStreamCode = 9;
+ // HTTP 500 — Internal Server Error
+ public const int InternalSystemErrorHttpStatusCode = 500;
+ public const int InternalSystemErrorStreamCode = -1;
+
//Stream
public int? StatusCode { get; }
public int? Code { get; }
@@ -72,12 +128,128 @@ private static string PrintExceptionFields(APIErrorInternalDTO apiError)
}
///
- /// Extensions for
+ /// Helpers for branching on the most common cases. Each
+ /// helper matches both the HTTP status and the Stream numeric error code so it stays
+ /// correct if the API ever reuses a status code for a different error.
///
public static class StreamApiExceptionExtensions
{
+ ///
+ /// HTTP 429 / Stream code 9. The caller exceeded the rate limit. Back off (typically with
+ /// exponential delay) before retrying.
+ ///
public static bool IsRateLimitExceededError(this StreamApiException streamApiException)
=> streamApiException.Code == StreamApiException.RateLimitErrorStreamCode &&
streamApiException.StatusCode == StreamApiException.RateLimitErrorHttpStatusCode;
+
+ ///
+ /// HTTP 403 / Stream code 60. The user sent a message while the channel's slow-mode cooldown
+ /// was active. Read IStreamChannel.Cooldown (in seconds) and gate the send UI for that
+ /// many seconds before allowing another send.
+ ///
+ public static bool IsCooldownError(this StreamApiException streamApiException)
+ => streamApiException.Code == StreamApiException.CooldownErrorStreamCode &&
+ streamApiException.StatusCode == StreamApiException.CooldownErrorHttpStatusCode;
+
+ ///
+ /// HTTP 403 / Stream code 17. The user lacks permission for the attempted action. Hide or
+ /// disable the corresponding UI control; do not retry.
+ ///
+ public static bool IsPermissionDeniedError(this StreamApiException streamApiException)
+ => streamApiException.Code == StreamApiException.PermissionDeniedErrorStreamCode &&
+ streamApiException.StatusCode == StreamApiException.PermissionDeniedErrorHttpStatusCode;
+
+ ///
+ /// HTTP 403 / Stream code 70. The user has no access to the requested channel(s). Hide the
+ /// channel from the local list or show an "access denied" placeholder.
+ ///
+ public static bool IsNoAccessToChannelsError(this StreamApiException streamApiException)
+ => streamApiException.Code == StreamApiException.NoAccessToChannelsErrorStreamCode &&
+ streamApiException.StatusCode == StreamApiException.NoAccessToChannelsErrorHttpStatusCode;
+
+ ///
+ /// HTTP 403 / Stream code 99. The Stream application has been suspended. Surface a
+ /// service-unavailable UI; the integration is offline until the app is reactivated.
+ ///
+ public static bool IsAppSuspendedError(this StreamApiException streamApiException)
+ => streamApiException.Code == StreamApiException.AppSuspendedErrorStreamCode &&
+ streamApiException.StatusCode == StreamApiException.AppSuspendedErrorHttpStatusCode;
+
+ ///
+ /// HTTP 401 / Stream code 5. Generic authentication failure (bad / missing token, wrong API
+ /// key, etc.). If you connected with an ITokenProvider the SDK refreshes automatically;
+ /// otherwise reconnect with a fresh token.
+ ///
+ public static bool IsAuthenticationError(this StreamApiException streamApiException)
+ => streamApiException.Code == StreamApiException.AuthenticationErrorStreamCode &&
+ streamApiException.StatusCode == StreamApiException.AuthenticationErrorHttpStatusCode;
+
+ ///
+ /// HTTP 401 / Stream code 40. The user token has expired. With an ITokenProvider
+ /// configured the SDK refreshes the token automatically; otherwise reconnect with a fresh
+ /// token from your backend.
+ ///
+ public static bool IsTokenExpiredError(this StreamApiException streamApiException)
+ => streamApiException.Code == StreamApiException.TokenExpiredErrorStreamCode &&
+ streamApiException.StatusCode == StreamApiException.TokenExpiredErrorHttpStatusCode;
+
+ ///
+ /// True for any of the token-related auth errors (expired / not-valid-yet / before-issued-at /
+ /// signature-invalid). Use this when you do not need to distinguish between them.
+ ///
+ public static bool IsTokenError(this StreamApiException streamApiException)
+ => streamApiException.StatusCode == 401 &&
+ (streamApiException.Code == StreamApiException.TokenExpiredErrorStreamCode ||
+ streamApiException.Code == StreamApiException.TokenNotValidYetErrorStreamCode ||
+ streamApiException.Code == StreamApiException.TokenBeforeIssuedAtErrorStreamCode ||
+ streamApiException.Code == StreamApiException.TokenSignatureInvalidErrorStreamCode);
+
+ ///
+ /// HTTP 404 / Stream code 16. The targeted resource (channel, message, user, …) does not
+ /// exist or was deleted. Refresh the local view; do not retry.
+ ///
+ public static bool IsDoesNotExistError(this StreamApiException streamApiException)
+ => streamApiException.Code == StreamApiException.DoesNotExistErrorStreamCode &&
+ streamApiException.StatusCode == StreamApiException.DoesNotExistErrorHttpStatusCode;
+
+ ///
+ /// HTTP 400 / Stream code 4. The request payload was invalid. Inspect
+ /// ExceptionFields for per-field validation messages and surface them to the user.
+ ///
+ public static bool IsInputError(this StreamApiException streamApiException)
+ => streamApiException.Code == StreamApiException.InputErrorStreamCode &&
+ streamApiException.StatusCode == StreamApiException.InputErrorHttpStatusCode;
+
+ ///
+ /// HTTP 400 / Stream code 20. The outgoing message exceeded the channel-type's
+ /// max_message_length. Show a character-limit error to the sender.
+ ///
+ public static bool IsMessageTooLongError(this StreamApiException streamApiException)
+ => streamApiException.Code == StreamApiException.MessageTooLongErrorStreamCode &&
+ streamApiException.StatusCode == StreamApiException.MessageTooLongErrorHttpStatusCode;
+
+ ///
+ /// HTTP 400 / Stream code 73. The message was rejected by moderation. Show a "your message
+ /// was filtered" UI to the sender; do not retry the identical payload.
+ ///
+ public static bool IsMessageModerationFailedError(this StreamApiException streamApiException)
+ => streamApiException.Code == StreamApiException.MessageModerationFailedErrorStreamCode &&
+ streamApiException.StatusCode == StreamApiException.MessageModerationFailedErrorHttpStatusCode;
+
+ ///
+ /// HTTP 413 / Stream code 22. The uploaded payload (e.g. file / image attachment) exceeded
+ /// the maximum allowed size. Ask the user to choose a smaller file.
+ ///
+ public static bool IsPayloadTooBigError(this StreamApiException streamApiException)
+ => streamApiException.Code == StreamApiException.PayloadTooBigErrorStreamCode &&
+ streamApiException.StatusCode == StreamApiException.PayloadTooBigErrorHttpStatusCode;
+
+ ///
+ /// HTTP 500 / Stream code -1. The server failed for an unspecified reason. Safe to retry
+ /// after a short delay; surface a transient-failure UI if it persists.
+ ///
+ public static bool IsInternalSystemError(this StreamApiException streamApiException)
+ => streamApiException.Code == StreamApiException.InternalSystemErrorStreamCode &&
+ streamApiException.StatusCode == StreamApiException.InternalSystemErrorHttpStatusCode;
}
-}
\ No newline at end of file
+}
diff --git a/Assets/Plugins/StreamChat/Core/StatefulModels/IStreamChannel.cs b/Assets/Plugins/StreamChat/Core/StatefulModels/IStreamChannel.cs
index 92d97fec..150d8776 100644
--- a/Assets/Plugins/StreamChat/Core/StatefulModels/IStreamChannel.cs
+++ b/Assets/Plugins/StreamChat/Core/StatefulModels/IStreamChannel.cs
@@ -399,7 +399,7 @@ Task BanMemberAsync(IStreamChannelMember member, string reason = "",
/// [Optional] reason description why user got banned
/// [Optional] timeout in minutes after which ban is automatically expired
/// [Optional] Should ban apply to user's IP address
- /// https://getstream.io/chat/docs/unity/moderation/?language=unreal#shadow-ban
+ /// https://getstream.io/chat/docs/unity/moderation/?language=unity#shadow-ban
Task ShadowBanUserAsync(IStreamUser user, string reason = "",
int? timeoutMinutes = default, bool isIpBan = false);
@@ -411,7 +411,7 @@ Task ShadowBanUserAsync(IStreamUser user, string reason = "",
/// [Optional] reason description why user got banned
/// [Optional] timeout in minutes after which ban is automatically expired
/// [Optional] Should ban apply to user's IP address
- /// https://getstream.io/chat/docs/unity/moderation/?language=unreal#shadow-ban
+ /// https://getstream.io/chat/docs/unity/moderation/?language=unity#shadow-ban
Task ShadowBanMemberAsync(IStreamChannelMember member, string reason = "",
int? timeoutMinutes = default, bool isIpBan = false);
diff --git a/Assets/Plugins/StreamChat/Samples/ChannelsCodeSamples.cs b/Assets/Plugins/StreamChat/Samples/ChannelsCodeSamples.cs
index 95ee5944..301beb66 100644
--- a/Assets/Plugins/StreamChat/Samples/ChannelsCodeSamples.cs
+++ b/Assets/Plugins/StreamChat/Samples/ChannelsCodeSamples.cs
@@ -28,7 +28,7 @@ public async Task CreateChannelUsingId()
}
///
- /// https://getstream.io/chat/docs/unity/creating_channels/?language=unreal#2.-creating-a-channel-for-a-list-of-members
+ /// https://getstream.io/chat/docs/unity/creating_channels/?language=unity#2.-creating-a-channel-for-a-list-of-members
///
public async Task CreateChannelForListOfMembers()
{
@@ -52,7 +52,7 @@ public async Task CreateChannelForListOfMembers()
#region Watch a channel
///
- /// https://getstream.io/chat/docs/unity/watch_channel/?language=unity#to-start-watching-a-channel
+ /// https://getstream.io/chat/docs/unity/creating-channels/?language=unity#watching-channels
///
public async Task StartWatchingChannel()
{
@@ -60,9 +60,9 @@ public async Task StartWatchingChannel()
{
UserFilter.Id.EqualsTo("other-user-id")
};
-// find user you want to start chat with
- var users = await Client.QueryUsersAsync(filters);
+// Find user you want to start chat with
+ var users = await Client.QueryUsersAsync(filters);
var otherUser = users.First();
var localUser = Client.LocalUserData.User;
@@ -70,15 +70,12 @@ public async Task StartWatchingChannel()
var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, channelId: "my-channel-id");
// Get channel with users combination
- var channelWithUsers
- = await Client.GetOrCreateChannelWithMembersAsync(ChannelType.Messaging,
- new[] { localUser, otherUser });
+ var channelWithUsers = await Client.GetOrCreateChannelWithMembersAsync(ChannelType.Messaging,
+ new[] { localUser, otherUser });
// Access properties
Debug.Log(channel.Name);
Debug.Log(channel.Members);
- Debug.Log(channel.Name);
- Debug.Log(channel.Name);
// Subscribe to events so you can react to updates
channel.MessageReceived += OnMessageReceived;
@@ -120,68 +117,61 @@ private void OnMessageReceived(IStreamChannel channel, IStreamMessage message)
}
///
- /// https://getstream.io/chat/docs/unity/watch_channel/?language=unity#watching-multiple-channels
+ /// https://getstream.io/chat/docs/unity/query-channels/?language=unity#channel-creation-and-watching
///
public async Task WatchingMultipleChannels()
{
- var localUser = Client.LocalUserData.User;
-
var filters = new IFieldFilterRule[]
{
// Get channels where local user is a member
- ChannelFilter.Members.In(localUser.Id)
+ ChannelFilter.Members.In(Client.LocalUserData.UserId)
};
+// Get channel state or create one if it doesn't exist
+ var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, "general");
+
+// Or get multiple channels matching a filter
var channels = await Client.QueryChannelsAsync(filters);
- // After query is done - loop channels and subscribe to events
- foreach (var channel in channels)
+ // Subscribe to per-channel events as needed
+ foreach (var c in channels)
{
- // Access properties
- Debug.Log(channel.Name);
- Debug.Log(channel.Members);
- Debug.Log(channel.Name);
- Debug.Log(channel.Name);
-
- // Subscribe to events so you can react to updates
- channel.MessageReceived += OnMessageReceived;
- channel.MessageUpdated += OnMessageUpdated;
- channel.MessageDeleted += OnMessageDeleted;
- channel.ReactionAdded += OnReactionAdded;
- channel.ReactionUpdated += OnReactionUpdated;
- channel.ReactionRemoved += OnReactionRemoved;
+ c.MessageReceived += OnMessageReceived;
+ c.MessageUpdated += OnMessageUpdated;
+ c.MessageDeleted += OnMessageDeleted;
+ c.ReactionAdded += OnReactionAdded;
+ c.ReactionUpdated += OnReactionUpdated;
+ c.ReactionRemoved += OnReactionRemoved;
}
}
///
- /// https://getstream.io/chat/docs/unity/watch_channel/?language=unity#watching-multiple-channels
+ /// https://getstream.io/chat/docs/unity/query-channels/?language=unity#sort-best-practices
///
public async Task WatchingMultipleChannels2()
{
- var localUser = Client.LocalUserData.User;
-
var filters = new IFieldFilterRule[]
{
// Get channels where local user is a member
- ChannelFilter.Members.In(localUser.Id)
+ ChannelFilter.Members.In(Client.LocalUserData.UserId)
};
- // You can also sort by various fields
var sort = ChannelSort.OrderByDescending(ChannelSortFieldName.LastMessageAt);
var channels = await Client.QueryChannelsAsync(filters, sort);
}
///
- /// https://getstream.io/chat/docs/unity/watch_channel/?language=unity#watching-multiple-channels
+ /// Code-only reference: no live Unity docs page demonstrates chained
+ /// channel sort (ThenByDescending) for any SDK, so this snippet
+ /// has no published home. Kept as a code-only example of how to chain
+ /// multiple sort fields against .
///
public async Task WatchingMultipleChannels3()
{
- var localUser = Client.LocalUserData.User;
-
var filters = new IFieldFilterRule[]
{
// Get channels where local user is a member
- ChannelFilter.Members.In(localUser.Id)
+ ChannelFilter.Members.In(Client.LocalUserData.UserId)
};
// You can sort by multiple fields and chain as many ThenByDescending as you need
@@ -192,7 +182,11 @@ public async Task WatchingMultipleChannels3()
}
///
- /// https://getstream.io/chat/docs/unity/watch_channel/?language=unity#stop-watching-a-channel
+ /// Code-only reference: creating-channels/?language=unity and
+ /// query-channels/?language=unity (the live targets of the old
+ /// watch_channel/ redirect) do not expose a "Stop watching a
+ /// channel" section for any SDK, so this snippet has no published home.
+ /// Kept as a code-only example of .
///
public async Task StopWatchingChannel()
{
@@ -202,7 +196,11 @@ public async Task StopWatchingChannel()
}
///
- /// https://getstream.io/chat/docs/unity/watch_channel/?language=unity#watcher-count
+ /// Code-only reference: creating-channels/?language=unity and
+ /// query-channels/?language=unity (the live targets of the old
+ /// watch_channel/ redirect) do not surface a "Watcher count"
+ /// section for any SDK. Kept as a code-only example of how to read
+ /// .
///
public async Task WatcherCount()
{
@@ -212,16 +210,12 @@ public async Task WatcherCount()
}
///
- /// https://getstream.io/chat/docs/unity/watch_channel/?language=unity#paginating-channel-watchers-with-channel.query
- ///
- public async Task PaginateChannelWatchers()
- {
- //StreamTodo: IMPLEMENT watchers pagination
- await Task.CompletedTask;
- }
-
- ///
- /// https://getstream.io/chat/docs/unity/watch_channel/?language=unity#listening-to-changes-in-watchers
+ /// Code-only reference: the old watch_channel/ page exposed
+ /// "Listening to changes in watchers" but the live targets of its
+ /// redirect (creating-channels/, query-channels/) do not
+ /// host a watcher-events section for any SDK. Kept as a code-only
+ /// example of /
+ /// .
///
public async Task ListenToChangesInWatchers()
{
@@ -419,7 +413,7 @@ public async Task AddAndRemoveModeratorsToChanel()
#region Querying Channels
///
- /// https://getstream.io/chat/docs/unity/query_channels/?language=unity
+ /// https://getstream.io/chat/docs/unity/query-channels/?language=unity
///
public async Task QueryChannels()
{
@@ -435,7 +429,12 @@ public async Task QueryChannels()
}
///
- /// https://getstream.io/chat/docs/unity/query_channels/?language=unity
+ /// Code-only reference: query-channels/?language=unity only
+ /// surfaces the single-filter Members.In(...) example (covered
+ /// by ); the extended
+ /// Members + Muted + MembersCount filter set is not published
+ /// for any SDK. Kept as a code-only example of combining multiple
+ /// rules in a single query.
///
public async Task QueryChannelsExtended()
{
@@ -451,23 +450,25 @@ public async Task QueryChannelsExtended()
}
///
- /// https://getstream.io/chat/docs/unity/query_channels/?language=unity#messaging-and-team
+ /// https://getstream.io/chat/docs/unity/query-channels/?language=unity#messaging-and-team-channels
///
public async Task MessagingAndTeam()
{
var filters = new List
{
- // Return only channels where local user is a member
+ ChannelFilter.Type.EqualsTo(ChannelType.Messaging),
ChannelFilter.Members.In(Client.LocalUserData.UserId),
-
- // You can define multiple filters that will all have to be satisfied
};
var channels = await Client.QueryChannelsAsync(filters);
}
///
- /// https://getstream.io/chat/docs/unity/query_channels/?language=unity#support
+ /// Code-only reference: query-channels/?language=unity does
+ /// not host a "Support" / customer-service filter example for any
+ /// SDK (the #support anchor on the old query_channels/
+ /// page no longer exists). Kept as a code-only example of querying
+ /// channels by custom fields via .
///
public async Task Support()
{
@@ -482,7 +483,7 @@ public async Task Support()
}
///
- /// https://getstream.io/chat/docs/unity/query_channels/?language=unity#pagination
+ /// https://getstream.io/chat/docs/unity/query-channels/?language=unity#pagination
///
public async Task QueryChannelsPagination()
{
@@ -490,13 +491,11 @@ public async Task QueryChannelsPagination()
{
// Return only channels where local user is a member
ChannelFilter.Members.In(Client.LocalUserData.UserId),
-
- // You can define multiple filters that will all have to be satisfied
};
-// Pass limit and offset to control the page or results returned
-// Limit - how many records per page
-// Offset - how many records to skip
+// Pass limit and offset to control pagination
+// Limit - records per page
+// Offset - records to skip
var channels = await Client.QueryChannelsAsync(filters, limit: 30, offset: 60);
}
@@ -532,7 +531,7 @@ public async Task QueryMembers()
#region Channel Pagination
///
- /// https://getstream.io/chat/docs/unity/channel_pagination/?language=unity
+ /// https://getstream.io/chat/docs/unity/channel-pagination/?language=unity#message-pagination
///
public async Task ChannelPaginateMessages()
{
@@ -543,30 +542,41 @@ public async Task ChannelPaginateMessages()
await channel.LoadOlderMessagesAsync();
}
- #endregion
-
///
- /// https://getstream.io/chat/docs/unity/channel_pagination/?language=unity
+ /// https://getstream.io/chat/docs/unity/channel-pagination/?language=unity#member-and-watcher-pagination
///
- public async Task ChannelPaginateMembers()
+ public async Task ChannelPaginateMembersAndWatchers()
{
var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, channelId: "my-channel-id");
- // StreamTodo: IMPLEMENT channel members pagination
- }
+// channel.Members exposes the loaded members snapshot (max 100 by default)
+ foreach (var member in channel.Members)
+ {
+ // ...
+ }
- ///
- /// https://getstream.io/chat/docs/unity/channel_pagination/?language=unity
- ///
- public async Task ChannelPaginateWatchers()
- {
- var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, channelId: "my-channel-id");
+// channel.Watchers exposes the loaded watchers snapshot (max 100 by default)
+ foreach (var watcher in channel.Watchers)
+ {
+ // ...
+ }
+
+// Use QueryMembersAsync to page beyond the loaded snapshot
+ var filters = new Dictionary();
+ var nextMembers = await channel.QueryMembersAsync(filters, limit: 20, offset: 0);
- // StreamTodo: IMPLEMENT channel watchers pagination
+// Paginating watchers beyond the loaded list is not yet available in the Unity SDK.
+// Please let us know if you'd like this feature implemented: https://github.com/GetStream/stream-chat-unity/issues
}
+ #endregion
+
///
- /// https://getstream.io/chat/docs/unity/channel_capabilities/?language=unity
+ /// Code-only reference: there is no Unity tab on
+ /// https://getstream.io/chat/docs/unity/chat-permission-policies/?language=unity
+ /// (or any other live docs page) that demonstrates reading
+ /// . The capability strings
+ /// are listed on https://getstream.io/chat/docs/unity/permissions-reference/?language=unity.
///
public async Task ChannelCapabilities()
{
@@ -576,8 +586,6 @@ public async Task ChannelCapabilities()
{
// User can update own message
}
-
- // Check action keys here https://getstream.io/chat/docs/unity/permissions_reference/?language=unity
}
///
@@ -668,25 +676,48 @@ public async Task QueryPendingInvites()
}
///
- /// https://getstream.io/chat/docs/unity/muting_channels/?language=unity#muting-channels
+ /// https://getstream.io/chat/docs/unity/muting-channels/?language=unity#mute-a-channel
///
public async Task MuteChannel()
{
var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, channelId: "my-channel-id");
+
+// Mute a channel for the current user
await channel.MuteChannelAsync();
+
+// Mute a channel for 2 weeks (duration in milliseconds)
+ await channel.MuteChannelAsync(milliseconds: 14 * 24 * 60 * 60 * 1000);
+
+// Mute a channel for 10 seconds
+ await channel.MuteChannelAsync(milliseconds: 10000);
+
+// The list of channel mutes (with expiration times) is available after connect
+ var channelMutes = Client.LocalUserData.ChannelMutes;
}
///
- /// https://getstream.io/chat/docs/unity/muting_channels/?language=unity#query-muted-channels
+ /// https://getstream.io/chat/docs/unity/muting-channels/?language=unity#query-muted-channels
///
public async Task QueryMutedChannels()
{
- //StreamTodo: IMPLEMENT query muted channels
- await Task.CompletedTask;
+// Retrieve all channels excluding muted ones
+ var notMutedFilters = new IFieldFilterRule[]
+ {
+ ChannelFilter.Members.In(Client.LocalUserData.UserId),
+ ChannelFilter.Muted.EqualsTo(false),
+ };
+ var notMutedChannels = await Client.QueryChannelsAsync(notMutedFilters);
+
+// Retrieve all muted channels
+ var mutedFilters = new IFieldFilterRule[]
+ {
+ ChannelFilter.Muted.EqualsTo(true),
+ };
+ var mutedChannels = await Client.QueryChannelsAsync(mutedFilters);
}
///
- /// https://getstream.io/chat/docs/unity/muting_channels/?language=unity#remove-a-channel-mute
+ /// https://getstream.io/chat/docs/unity/muting-channels/?language=unity#remove-a-channel-mute
///
public async Task RemoveChannelMute()
{
@@ -695,7 +726,7 @@ public async Task RemoveChannelMute()
}
///
- /// https://getstream.io/chat/docs/unity/muting_channels/?language=unity#hiding-a-channel
+ /// https://getstream.io/chat/docs/unity/hiding-channels/?language=unity#hide-a-channel
///
public async Task HideAndShowChannel()
{
@@ -780,11 +811,22 @@ public async Task TruncateChannel()
}
///
- /// https://getstream.io/chat/docs/unity/slow_mode/?language=unity
+ /// https://getstream.io/chat/docs/unity/slow-mode/?language=unity#channel-slow-mode
+ ///
+ public async Task EnableDisableSlowMode()
+ {
+// This feature is not yet available in the Unity SDK.
+// Please let us know if you'd like this feature implemented: https://github.com/GetStream/stream-chat-unity/issues
+ await Task.CompletedTask;
+ }
+
+ ///
+ /// https://getstream.io/chat/docs/unity/slow-mode/?language=unity#channel-slow-mode
///
- public async Task ThrottleAndSlowMode()
+ public async Task ReadChannelCooldown()
{
- //StreamTodo: IMPLEMENT Throttle and slow mode
+// This feature is not yet available in the Unity SDK.
+// Please let us know if you'd like this feature implemented: https://github.com/GetStream/stream-chat-unity/issues
await Task.CompletedTask;
}
diff --git a/Assets/Plugins/StreamChat/Samples/ClientAndUsersCodeSamples.cs b/Assets/Plugins/StreamChat/Samples/ClientAndUsersCodeSamples.cs
index 0dc7b442..72a949f2 100644
--- a/Assets/Plugins/StreamChat/Samples/ClientAndUsersCodeSamples.cs
+++ b/Assets/Plugins/StreamChat/Samples/ClientAndUsersCodeSamples.cs
@@ -14,7 +14,7 @@ namespace StreamChat.Samples
internal sealed class ClientAndUsersCodeSamples
{
///
- /// https://getstream.io/chat/docs/unity/tokens_and_authentication/?language=unity#developer-tokens
+ /// https://getstream.io/chat/docs/unity/tokens-and-authentication/?language=unity#developer-tokens
///
public async Task DeveloperTokens()
{
@@ -27,11 +27,11 @@ public async Task DeveloperTokens()
var client = StreamChatClient.CreateDefaultClient();
// Connect user
- var localUserData = await client.ConnectUserAsync("API_KEY", userId, userToken);
+ var localUserData = await client.ConnectUserAsync(credentials);
}
///
- /// https://getstream.io/chat/docs/unity/init_and_users/?language=unity
+ /// https://getstream.io/chat/docs/unity/init-and-users/?language=unity
///
public void InitClient()
{
@@ -39,36 +39,35 @@ public void InitClient()
}
///
- /// https://getstream.io/chat/docs/unity/init_and_users/?language=unreal#connecting-the-user
+ /// https://getstream.io/chat/docs/unity/init-and-users/?language=unity#connecting-users
///
public async Task ConnectUser()
{
var client = StreamChatClient.CreateDefaultClient();
+// 1. Static JWT token - prototyping/tests only. In production, issue tokens
+// from your backend.
var localUserData = await client.ConnectUserAsync("api_key", "chat_user", "chat_user_token");
-// After await is complete the user is connected
+// Await returns once connected; you can also subscribe to client.Connected.
+ client.Connected += connectedUser => { /* User is connected */ };
+// 2. Production: implement ITokenProvider. The SDK calls GetTokenAsync on
+// initial connect, reconnect, and expiration, so the WebSocket stays
+// connected across token refreshes automatically.
+ var tokenProvider = new YourTokenProvider();
+ await client.ConnectUserAsync("api_key", "chat_user", tokenProvider);
}
-
- ///
- /// https://getstream.io/chat/docs/unity/init_and_users/?language=unreal#connecting-the-user
- ///
- public async Task ConnectUser2()
- {
- var client = StreamChatClient.CreateDefaultClient();
-
- await client.ConnectUserAsync("api_key", "chat_user", "chat_user_token");
-// After await is complete the user is connected
-// Alternatively, you subscribe to the IStreamChatClient.Connected event
- client.Connected += localUserData =>
- {
- // User is connected
- };
+// Your backend MUST authenticate the caller before issuing a Stream token -
+// never expose an endpoint that returns a token for any userId.
+ public class YourTokenProvider : ITokenProvider
+ {
+ public Task GetTokenAsync(string userId)
+ => Task.FromResult("token-fetched-from-your-backend");
}
///
- /// https://getstream.io/chat/docs/unity/init_and_users/?language=unity#websocket-connections
+ /// https://getstream.io/chat/docs/unity/init-and-users/?language=unity#disconnecting-users
///
public async Task DisconnectUser()
{
@@ -76,29 +75,10 @@ public async Task DisconnectUser()
await client.DisconnectUserAsync();
}
- // Managing users https://getstream.io/chat/docs/unity/update_users/?language=unity
-
- ///
- /// https://getstream.io/chat/docs/unity/update_users/?language=unity#delete-a-user
- ///
- public void DeleteUser()
- {
- //StreamTODO: Implement user delete
- }
-
- ///
- /// https://getstream.io/chat/docs/unity/logout/?language=unity
- ///
- public async Task LogoutUser()
- {
- await Client.DisconnectUserAsync();
- }
-
-
#region Managing Users
///
- /// https://getstream.io/chat/docs/unity/update_users/?language=unity#server-side-user-updates-(batch)
+ /// https://getstream.io/chat/docs/unity/update-users/?language=unity#creating-and-updating-users-server-side
///
public async Task UserUpdates()
{
@@ -123,6 +103,9 @@ public async Task UserUpdates()
var users = await Client.UpsertUsersAsync(new[] { createOrUpdateUser });
}
+ ///
+ /// https://getstream.io/chat/docs/unity/update-users/?language=unity#creating-and-updating-users-server-side
+ ///
public async Task UserUpdatesMultiple()
{
var usersToCreateOrUpdate = new[]
@@ -153,26 +136,87 @@ public async Task UserUpdatesMultiple()
var users = await Client.UpsertUsersAsync(usersToCreateOrUpdate);
}
+ ///
+ /// https://getstream.io/chat/docs/unity/update-users/?language=unity#server-side-partial-updates
+ ///
+ public void PartialUpdateUser()
+ {
+// This is a server-side only feature, choose any of our server-side SDKs to use it
+ }
+
+ ///
+ /// https://getstream.io/chat/docs/unity/update-users/?language=unity#unique-usernames
+ ///
+ public void UniqueUsernames()
+ {
+// This can be set in https://dashboard.getstream.io/ -> Open your application -> Overview -> Authentication
+ }
+
+ ///
+ /// https://getstream.io/chat/docs/unity/update-users/?language=unity#deactivate-a-user
+ ///
+ public void DeactivateUser()
+ {
+// This is a server-side only feature, choose any of our server-side SDKs to use it
+ }
+
+ ///
+ /// https://getstream.io/chat/docs/unity/update-users/?language=unity#deactivate-many-users
+ ///
+ public void DeactivateManyUsers()
+ {
+// This is a server-side only feature, choose any of our server-side SDKs to use it
+ }
+
+ ///
+ /// https://getstream.io/chat/docs/unity/update-users/?language=unity#reactivate-a-user
+ ///
+ public void ReactivateUser()
+ {
+// This is a server-side only feature, choose any of our server-side SDKs to use it
+ }
+
+ ///
+ /// https://getstream.io/chat/docs/unity/update-users/?language=unity#deleting-many-users
+ ///
+ public void DeleteUsers()
+ {
+// This is a server-side only feature, choose any of our server-side SDKs to use it
+ }
+
+ ///
+ /// https://getstream.io/chat/docs/unity/update-users/?language=unity#restoring-deleted-users
+ ///
+ public void RestoreUsers()
+ {
+// This is a server-side only feature, choose any of our server-side SDKs to use it
+ }
+
#endregion
#region Querying Users
///
- /// https://getstream.io/chat/docs/unity/query_users/?language=unity
+ /// https://getstream.io/chat/docs/unity/update-users/?language=unity#querying-users
///
public async Task QueryUsers()
{
var filters = new IFieldFilterRule[]
{
- UserFilter.Id.In("user-1", "user-2", "user-3")
+ UserFilter.Id.In("john", "jack", "jessie")
};
+
+ var sort = UsersSort.OrderByDescending(UserSortField.LastActive);
+ var limit = 10;
+ var offset = 0;
+
// Returns collection of IStreamUser
- var users = await Client.QueryUsersAsync(filters);
+ var users = await Client.QueryUsersAsync(filters, sort, offset, limit);
}
- ///
- /// https://getstream.io/chat/docs/unity/query_users/?language=unity
- ///
+ // Code-only reference: the docs page does not host a separate offset/limit
+ // pagination snippet for QueryUsers (the limit + offset arguments are
+ // demonstrated inside the main "Querying Users" tab).
public async Task QueryUsersPagination()
{
var lastWeek = DateTime.Now.AddDays(-7);
@@ -186,41 +230,50 @@ public async Task QueryUsersPagination()
var limit = 30; // How many records per page
var offset = 0; // How many records to skip e.g. offset = 30 -> page 2, offset = 60 -> page 3, etc.
-
+
// Returns collection of IStreamUser
var users = await Client.QueryUsersAsync(filters, sort, offset, limit);
}
///
- /// https://getstream.io/chat/docs/unity/query_users/?language=unity#1.-by-name
+ /// https://getstream.io/chat/docs/unity/update-users/?language=unity#querying-with-autocomplete
///
public async Task QueryUsersUsingAutocompleteByName()
{
var filters = new IFieldFilterRule[]
{
- UserFilter.Name.Autocomplete("Ro")
+ UserFilter.Name.Autocomplete("Ro")
};
// Returns collection of IStreamUser
var users = await Client.QueryUsersAsync(filters);
}
- ///
- /// https://getstream.io/chat/docs/unity/query_users/?language=unity#2.-by-id
- ///
+ // Code-only reference: the docs page only hosts a single autocomplete
+ // snippet (under "Querying with Autocomplete") which is mirrored by
+ // QueryUsersUsingAutocompleteByName above.
public async Task QueryUsersUsingAutocompleteById()
{
var filters = new IFieldFilterRule[]
{
- // Return all users with Id starting with `Ro` like: Roxy, Roxanne, Rover
- UserFilter.Name.Autocomplete("Ro")
+ // Return all users whose Id starts with `Ro` (e.g. Roxy, Roxanne, Rover)
+ UserFilter.Id.Autocomplete("Ro")
};
// Returns collection of IStreamUser
var users = await Client.QueryUsersAsync(filters);
}
///
- /// https://getstream.io/chat/docs/unity/query_users/?language=unity
+ /// https://getstream.io/chat/docs/unity/update-users/?language=unity#querying-inactive-users
///
+ public void QueryInactiveUsers()
+ {
+// The $exists operator on last_active is not yet exposed in the Unity SDK.
+// Use the UserFilter.LastActive comparison operators (e.g. LessThan / GreaterThanOrEquals)
+// to filter users by their last-active timestamp.
+ }
+
+ // Code-only reference: QueryBannedUsersAsync targets a different endpoint
+ // that is not documented on the Managing Users page.
public async Task QueryBannedUsers()
{
// Returns collection of StreamUserBanInfo
diff --git a/Assets/Plugins/StreamChat/Samples/EventsSamples.cs b/Assets/Plugins/StreamChat/Samples/EventsSamples.cs
index 18b7792f..bc03861f 100644
--- a/Assets/Plugins/StreamChat/Samples/EventsSamples.cs
+++ b/Assets/Plugins/StreamChat/Samples/EventsSamples.cs
@@ -1,4 +1,6 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
+using System.Linq;
using System.Threading.Tasks;
using StreamChat.Core;
using StreamChat.Core.Models;
@@ -10,18 +12,35 @@ namespace StreamChat.Samples
{
internal class EventsSamples
{
- public async Task QueryChannelsEvents()
+ ///
+ /// https://getstream.io/chat/docs/unity/event-object/?language=unity#listening-for-events
+ ///
+ public async Task ListeningForEvents()
{
- // Get a single channel
+ // 1. Client-level events
+ // Fire for the local user, regardless of which channels/messages/etc. are loaded.
+ Client.Connected += OnConnected;
+ Client.Disconnected += OnDisconnected;
+ Client.ConnectionStateChanged += OnConnectionStateChanged;
+ Client.AddedToChannelAsMember += OnAddedToChannelAsMember;
+ Client.RemovedFromChannelAsMember += OnRemovedFromChannelAsMember;
+ Client.ChannelDeleted += OnChannelDeleted;
+ Client.ChannelInviteReceived += OnChannelInviteReceived;
+ Client.ChannelInviteAccepted += OnChannelInviteAccepted;
+ Client.ChannelInviteRejected += OnChannelInviteRejected;
+ Client.ThreadTracked += OnThreadTracked;
+ Client.ThreadUntracked += OnThreadUntracked;
+
+ // 2. Get a channel (or many) you want to listen on
var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, "my-channel-id");
- // Or multiple with optional filters
- var channels = await Client.QueryChannelsAsync(new List()
+ var channels = await Client.QueryChannelsAsync(new List
{
ChannelFilter.Members.In(Client.LocalUserData.User)
});
- // Subscribe to events
+ // 3. Channel-level events
+ // Fire only for channels you are watching (loaded via GetOrCreate* / QueryChannelsAsync).
channel.MessageReceived += OnMessageReceived;
channel.MessageUpdated += OnMessageUpdated;
channel.MessageDeleted += OnMessageDeleted;
@@ -33,19 +52,104 @@ public async Task QueryChannelsEvents()
channel.MemberAdded += OnMemberAdded;
channel.MemberRemoved += OnMemberRemoved;
channel.MemberUpdated += OnMemberUpdated;
-
channel.MembersChanged += OnMembersChanged;
+
channel.VisibilityChanged += OnVisibilityChanged;
channel.MuteChanged += OnMuteChanged;
channel.Truncated += OnTruncated;
channel.Updated += OnUpdated;
+
channel.WatcherAdded += OnWatcherAdded;
channel.WatcherRemoved += OnWatcherRemoved;
+
channel.UserStartedTyping += OnUserStartedTyping;
channel.UserStoppedTyping += OnUserStoppedTyping;
channel.TypingUsersChanged += OnTypingUsersChanged;
+
+ // 4. Per-message events
+ // Reaction events fire on the specific IStreamMessage instance.
+ var message = channel.Messages.First();
+ message.ReactionAdded += OnReactionAdded;
+ message.ReactionUpdated += OnReactionUpdated;
+ message.ReactionRemoved += OnReactionRemoved;
+
+ // 5. Per-thread events
+ // Load a thread via GetThreadAsync / QueryThreadsAsync (or via a channel that has tracked threads).
+ var thread = await Client.GetThreadAsync(message.Id);
+ thread.Updated += OnThreadUpdated;
+ thread.ReplyReceived += OnThreadReplyReceived;
+ thread.ReadStateChanged += OnThreadReadStateChanged;
+
+ // 6. Per-user events
+ // Any IStreamUser instance (e.g. from channel.Members) exposes presence updates.
+ var user = channel.Members.First().User;
+ user.PresenceChanged += OnUserPresenceChanged;
+
+ // 7. Per-poll events
+ // Load a poll via Client.Polls.GetPollAsync, or take it from a message that has one attached.
+ var poll = await Client.Polls.GetPollAsync("poll-id");
+ poll.Closed += OnPollClosed;
+ poll.Updated += OnPollUpdated;
+ poll.VoteCasted += OnPollVoteCasted;
+ poll.VoteChanged += OnPollVoteChanged;
+ poll.VoteRemoved += OnPollVoteRemoved;
+ }
+
+ // ---- Client-level handlers ----
+
+ private void OnConnected(IStreamLocalUserData localUserData)
+ {
+ }
+
+ private void OnDisconnected()
+ {
+ }
+
+ private void OnConnectionStateChanged(ConnectionState previous, ConnectionState current)
+ {
+ }
+
+ private void OnAddedToChannelAsMember(IStreamChannel channel, IStreamChannelMember member)
+ {
+ // Fires for channels the local user was just added to and that are not yet watched locally.
+ // For watched channels, use channel.MemberAdded instead.
+ }
+
+ private void OnRemovedFromChannelAsMember(IStreamChannel channel, IStreamChannelMember member)
+ {
+ // Fires for channels the local user was just removed from and that are not yet watched locally.
+ // For watched channels, use channel.MemberRemoved instead.
+ }
+
+ private void OnChannelDeleted(string channelCid, string channelId, ChannelType channelType)
+ {
+ }
+
+ private void OnChannelInviteReceived(IStreamChannel channel, IStreamUser invitee)
+ {
+ }
+
+ private void OnChannelInviteAccepted(IStreamChannel channel, IStreamUser invitee)
+ {
+ }
+
+ private void OnChannelInviteRejected(IStreamChannel channel, IStreamUser invitee)
+ {
+ }
+
+ private void OnThreadTracked(IStreamThread thread)
+ {
+ // Fires when an IStreamThread becomes available locally (after GetThreadAsync, QueryThreadsAsync,
+ // or when watching a channel that contains threads). Use this to bind per-thread UI.
}
+ private void OnThreadUntracked(IStreamThread thread)
+ {
+ // Fires when a tracked thread is no longer available (e.g. the parent message was hard-deleted).
+ }
+
+ // ---- Channel-level handlers ----
+
private void OnMessageReceived(IStreamChannel channel, IStreamMessage message)
{
}
@@ -54,10 +158,11 @@ private void OnMessageUpdated(IStreamChannel channel, IStreamMessage message)
{
}
- private void OnMessageDeleted(IStreamChannel channel, IStreamMessage message, bool isharddelete)
+ private void OnMessageDeleted(IStreamChannel channel, IStreamMessage message, bool isHardDelete)
{
}
+ // Reused for both channel.Reaction* and message.Reaction* (same delegate signature).
private void OnReactionAdded(IStreamChannel channel, IStreamMessage message, StreamReaction reaction)
{
}
@@ -86,7 +191,7 @@ private void OnMembersChanged(IStreamChannel channel, IStreamChannelMember membe
{
}
- private void OnVisibilityChanged(IStreamChannel channel, bool isVisible)
+ private void OnVisibilityChanged(IStreamChannel channel, bool isHidden)
{
}
@@ -122,43 +227,88 @@ private void OnTypingUsersChanged(IStreamChannel channel)
{
}
- public void SubscribeToClientEvents()
+ // ---- Thread-level handlers ----
+
+ private void OnThreadUpdated(IStreamThread thread)
{
- Client.AddedToChannelAsMember += OnAddedToChannelAsMember;
- Client.RemovedFromChannelAsMember += OnRemovedFromChannel;
}
- private void OnAddedToChannelAsMember(IStreamChannel channel, IStreamChannelMember member)
+ private void OnThreadReplyReceived(IStreamThread thread, IStreamMessage reply)
{
- // channel - new channel to which local user was just added
- // member - object containing channel membership information
}
- private void OnRemovedFromChannel(IStreamChannel channel, IStreamChannelMember member)
+ private void OnThreadReadStateChanged(IStreamThread thread)
{
- // channel - channel from which local user was removed
- // member - object containing channel membership information
}
- public void SubscribeToConnectionEvents()
+ // ---- User-level handlers ----
+
+ private void OnUserPresenceChanged(IStreamUser user, bool isOnline, DateTimeOffset? lastActive)
{
- Client.Connected += OnConnected;
- Client.Disconnected += OnDisconnected;
- Client.ConnectionStateChanged += OnConnectionStateChanged;
}
- private void OnConnected(IStreamLocalUserData localUserData)
+ // ---- Poll-level handlers ----
+
+ private void OnPollClosed(IStreamPoll poll)
{
}
- private void OnDisconnected()
+ private void OnPollUpdated(IStreamPoll poll)
{
}
- private void OnConnectionStateChanged(ConnectionState previous, ConnectionState current)
+ private void OnPollVoteCasted(IStreamPoll poll, StreamPollVote vote)
+ {
+ }
+
+ private void OnPollVoteChanged(IStreamPoll poll, StreamPollVote vote)
+ {
+ }
+
+ private void OnPollVoteRemoved(IStreamPoll poll, StreamPollVote vote)
{
}
+ ///
+ /// https://getstream.io/chat/docs/unity/event-object/?language=unity#listening-for-events
+ /// (the "You can also listen to all events at once" Unity tab)
+ ///
+ public void ListenToAllEventsAtOnce()
+ {
+ // Not supported in the Unity SDK
+ }
+
+ ///
+ /// https://getstream.io/chat/docs/unity/event-object/?language=unity#event-types
+ ///
+ public async Task ListenForUserPresenceEvents()
+ {
+ // Get a channel
+ var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, "my-channel-id");
+
+ // Each user object exposes the PresenceChanged event
+ foreach (var member in channel.Members)
+ {
+ member.User.PresenceChanged += (userObj, isOnline, isActive) =>
+ {
+ // Handle presence change
+ };
+ }
+ }
+
+ ///
+ /// https://getstream.io/chat/docs/unity/event-object/?language=unity#connection-events
+ ///
+ public void SubscribeToConnectionEvents()
+ {
+ Client.Connected += OnConnected;
+ Client.Disconnected += OnDisconnected;
+ Client.ConnectionStateChanged += OnConnectionStateChanged;
+ }
+
+ ///
+ /// https://getstream.io/chat/docs/unity/event-object/?language=unity#stop-listening-for-events
+ ///
public void Unsubscribe()
{
Client.Connected -= OnConnected;
@@ -166,6 +316,22 @@ public void Unsubscribe()
Client.ConnectionStateChanged -= OnConnectionStateChanged;
}
+ ///
+ /// https://getstream.io/chat/docs/unity/event-object/?language=unity#to-a-channel
+ ///
+ public void SendCustomEventToChannel()
+ {
+ // Not yet supported in the Unity SDK
+ }
+
+ ///
+ /// https://getstream.io/chat/docs/unity/event-object/?language=unity#to-a-user
+ ///
+ public void SendCustomEventToUser()
+ {
+ // Not yet supported in the Unity SDK
+ }
+
private IStreamChatClient Client { get; } = StreamChatClient.CreateDefaultClient();
}
-}
\ No newline at end of file
+}
diff --git a/Assets/Plugins/StreamChat/Samples/MessagesCodeSamples.cs b/Assets/Plugins/StreamChat/Samples/MessagesCodeSamples.cs
index 81a0e41f..9c3bd701 100644
--- a/Assets/Plugins/StreamChat/Samples/MessagesCodeSamples.cs
+++ b/Assets/Plugins/StreamChat/Samples/MessagesCodeSamples.cs
@@ -3,7 +3,10 @@
using System.IO;
using System.Threading.Tasks;
using StreamChat.Core;
-using StreamChat.Core.LowLevelClient.Requests;
+using StreamChat.Core.QueryBuilders.Filters;
+using StreamChat.Core.QueryBuilders.Filters.Channels;
+using StreamChat.Core.QueryBuilders.Filters.Messages;
+using StreamChat.Core.QueryBuilders.Sort;
using StreamChat.Core.Requests;
using StreamChat.Core.StatefulModels;
using UnityEngine;
@@ -13,33 +16,73 @@ namespace StreamChat.Samples
internal sealed class MessagesCodeSamples
{
///
- /// https://getstream.io/chat/docs/unity/send_message/?language=unity
+ /// https://getstream.io/chat/docs/unity/send-message/?language=unity#sending-a-message
///
public async Task Overview()
{
var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, channelId: "my-channel-id");
- var message = await channel.SendNewMessageAsync("Hello world!");
+ var message = await channel.SendNewMessageAsync("Hello, world!");
}
///
- /// https://getstream.io/chat/docs/unity/send_message/?language=unity#complex-example
+ /// https://getstream.io/chat/docs/unity/send-message/?language=unity#sending-messages-with-attachments
///
+ public async Task SendingMessagesWithAttachments()
+ {
+ var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, channelId: "my-channel-id");
+
+ IStreamUser josh = null;
+
+ var message = await channel.SendNewMessageAsync(new StreamSendMessageRequest
+ {
+ Text = "@Josh Check out this image!",
+
+ // All fields below are optional
+ Attachments = new List
+ {
+ new StreamAttachmentRequest
+ {
+ Type = "image",
+ AssetUrl = "https://bit.ly/2K74TaG",
+ ThumbUrl = "https://bit.ly/2Uumxti",
+ }
+ },
+ MentionedUsers = new List { josh },
+ Pinned = true,
+ PinExpires = new DateTimeOffset(DateTime.Now).AddDays(7),
+ CustomData = new StreamCustomDataRequest
+ {
+ { "priority", "high" }
+ }
+ });
+ }
+
+ // Code-only reference (kitchen-sink demo of every send-message option the
+ // stateful Unity SDK exposes — threading, quoting, pinning, mentions,
+ // and custom data in a single call). The published docs do not host an
+ // equivalent "complex example" group on /send-message/ since the page
+ // restructure on 2025-12-11 (commit 3ccdc786e), so per the orphan rule
+ // there is no `///` URL to point at — the page now splits these
+ // features across /threads/ (parent_id, show_in_channel),
+ // /pinned-messages/ (pinned, pin_expires) and the section-specific
+ // tabs above. Kept as a single grep-friendly reference for Unity
+ // readers who want to see every option in one spot.
public async Task ComplexExample()
{
var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, "my-channel-id");
IStreamUser someUser = null;
-// Send simple message with text only
+ // Send a quoted message
var message3 = await channel.SendNewMessageAsync("Hello");
-// Send simple message with text only
+ // Send a parent message we can reply to in a thread
var message2 = await channel.SendNewMessageAsync("Let's start a thread!");
var message = await channel.SendNewMessageAsync(new StreamSendMessageRequest
{
MentionedUsers = new List { someUser }, // Mention a user
- ParentId = message2.Id, // Write in thread
+ ParentId = message2.Id, // Write in a thread
PinExpires = new DateTimeOffset(DateTime.Now).AddDays(7), // Pin for 7 days
Pinned = true,
QuotedMessage = message3,
@@ -61,20 +104,19 @@ public async Task GetMessageById()
}
///
- /// https://getstream.io/chat/docs/unity/send_message/?language=unity#update-a-message
+ /// https://getstream.io/chat/docs/unity/send-message/?language=unity#updating-a-message
///
public async Task UpdateAMessage()
{
var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, channelId: "my-channel-id");
- var message = await channel.SendNewMessageAsync("Hello world!");
+ var message = await channel.SendNewMessageAsync("Hello, world!");
-// Edit message text and some custom data
await message.UpdateAsync(new StreamUpdateMessageRequest
{
- Text = "Hi everyone!",
+ Text = "Updated message text",
CustomData = new StreamCustomDataRequest
{
- { "tags", new[] { "Funny", "Unique" } }
+ { "tags", new[] { "edited" } }
}
});
}
@@ -88,17 +130,17 @@ public async Task PartialUpdate()
}
///
- /// https://getstream.io/chat/docs/unity/send_message/?language=unity#delete-a-message
+ /// https://getstream.io/chat/docs/unity/send-message/?language=unity#deleting-a-message
///
public async Task DeleteAMessage()
{
var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, channelId: "my-channel-id");
- var message = await channel.SendNewMessageAsync("Hello world!");
+ var message = await channel.SendNewMessageAsync("Hello, world!");
-// Soft delete
+ // Soft delete
await message.SoftDeleteAsync();
-// Hard delete
+ // Hard delete
await message.HardDeleteAsync();
}
@@ -111,25 +153,35 @@ public async Task OpenGraphScrapper()
}
///
- /// https://getstream.io/chat/docs/unity/file_uploads/?language=unity#how-to-upload-a-file-or-image
+ /// https://getstream.io/chat/docs/unity/file-uploads/?language=unity#uploading-files-to-a-channel
///
public async Task UploadFileOrImage()
{
var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, channelId: "my-channel-id");
-// Get file byte array however you want e.g. Addressables.LoadAsset, Resources.Load, etc.
- var sampleFile = File.ReadAllBytes("path/to/file");
- var fileUploadResponse = await channel.UploadFileAsync(sampleFile, "my-file-name");
- var fileWebUrl = fileUploadResponse.FileUrl;
+ var imageBytes = File.ReadAllBytes("path/to/image.jpg");
+ var imageUploadResponse = await channel.UploadImageAsync(imageBytes, "image.jpg");
+ var imageUrl = imageUploadResponse.FileUrl;
-// Get image byte array however you want e.g. Addressables.LoadAsset, Resources.Load, etc.
- var sampleImage = File.ReadAllBytes("path/to/file");
- var imageUploadResponse = await channel.UploadImageAsync(sampleFile, "my-image-name");
- var imageWebUrl = imageUploadResponse.FileUrl;
+ var fileBytes = File.ReadAllBytes("path/to/document.pdf");
+ var fileUploadResponse = await channel.UploadFileAsync(fileBytes, "document.pdf");
+ var fileUrl = fileUploadResponse.FileUrl;
}
///
- /// https://getstream.io/chat/docs/unity/file_uploads/?language=unity#deleting-files-and-images
+ /// https://getstream.io/chat/docs/unity/file-uploads/?language=unity#uploading-standalone-files
+ ///
+ public Task UploadStandaloneFiles()
+ {
+ // Uploading standalone files via a client-level endpoint is not yet available in the Unity SDK.
+ // Please let us know if you'd like this feature implemented: https://github.com/GetStream/stream-chat-unity/issues
+ // As a workaround, upload via channel.UploadImageAsync / channel.UploadFileAsync
+ // and reuse the returned URL when upserting a user with Client.UpsertUsersAsync.
+ return Task.CompletedTask;
+ }
+
+ ///
+ /// https://getstream.io/chat/docs/unity/file-uploads/?language=unity#deleting-files
///
public async Task DeleteFileOrImage()
{
@@ -138,13 +190,13 @@ public async Task DeleteFileOrImage()
}
///
- /// https://getstream.io/chat/docs/unity/file_uploads/?language=unity#using-your-own-cdn
+ /// https://getstream.io/chat/docs/unity/file-uploads/?language=unity#using-your-own-cdn
///
public async Task UsingYourOwnCdn()
{
var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, channelId: "my-channel-id");
-//Implement your own CDN upload and obtain the file URL
+ // Upload to your CDN and obtain the file URL
var fileUrl = "file-url-to-your-cdn";
await channel.SendNewMessageAsync(new StreamSendMessageRequest
@@ -158,33 +210,28 @@ await channel.SendNewMessageAsync(new StreamSendMessageRequest
}
}
});
-
- await Task.CompletedTask;
}
///
- /// https://getstream.io/chat/docs/unity/send_reaction/?language=unity
+ /// https://getstream.io/chat/docs/unity/send-reaction/?language=unity#sending-a-reaction
///
public async Task ReactionOverview()
{
var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, channelId: "my-channel-id");
var message = await channel.SendNewMessageAsync("Hello world!");
-// Send simple reaction with a score of 1
+ // Send a reaction with default score of 1
await message.SendReactionAsync("like");
-// Send reaction with a custom score value
- await message.SendReactionAsync("clap", 10);
-
-// Send reaction with a custom score value
+ // Send a reaction with custom score
await message.SendReactionAsync("clap", 10);
-// Send reaction and replace all previous reactions (if any) from this user
+ // Replace all previous reactions from this user
await message.SendReactionAsync("love", enforceUnique: true);
}
///
- /// https://getstream.io/chat/docs/unity/send_reaction/?language=unity#removing-a-reaction
+ /// https://getstream.io/chat/docs/unity/send-reaction/?language=unity#removing-a-reaction
///
public async Task RemoveReaction()
{
@@ -195,25 +242,61 @@ public async Task RemoveReaction()
}
///
- /// https://getstream.io/chat/docs/unity/send_reaction/?language=unity#paginating-reactions
+ /// https://getstream.io/chat/docs/unity/send-reaction/?language=unity#paginating-reactions
///
public async Task PaginateReactions()
{
var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, channelId: "my-channel-id");
var message = await channel.SendNewMessageAsync("Hello world!");
- //StreamTodo: IMPLEMENT reactions paginating
+ // Paginating reactions via API is not yet available in the Unity SDK.
+ // Please let us know if you'd like this feature implemented: https://github.com/GetStream/stream-chat-unity/issues
+ // Each message exposes a snapshot of the reaction summary instead:
+
+ // The 10 most recent reactions on the message
+ foreach (var reaction in message.LatestReactions)
+ {
+ Debug.Log($"{reaction.Type} from {reaction.User?.Id}");
+ }
+
+ // The 10 most recent reactions left by the local user
+ foreach (var reaction in message.OwnReactions)
+ {
+ Debug.Log($"My reaction: {reaction.Type}");
+ }
+
+ // Number of reactions per type, e.g. {"love": 3, "fire": 2}
+ foreach (var entry in message.ReactionCounts)
+ {
+ Debug.Log($"{entry.Key}: {entry.Value} reactions");
+ }
+
+ // Sum of reaction scores per type (matches counts unless cumulative reactions are used)
+ foreach (var entry in message.ReactionScores)
+ {
+ Debug.Log($"{entry.Key}: total score {entry.Value}");
+ }
}
///
- /// https://getstream.io/chat/docs/unity/send_reaction/?language=unity#cumulative-(clap)-reactions
+ /// https://getstream.io/chat/docs/unity/send-reaction/?language=unity#querying-reactions
+ ///
+ public async Task QueryReactions()
+ {
+ // This feature is not yet available in the Unity SDK.
+ // Please let us know if you'd like this feature implemented: https://github.com/GetStream/stream-chat-unity/issues
+ await Task.CompletedTask;
+ }
+
+ ///
+ /// https://getstream.io/chat/docs/unity/send-reaction/?language=unity#cumulative-reactions
///
public async Task CumulativeReactions()
{
var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, channelId: "my-channel-id");
var message = await channel.SendNewMessageAsync("Hello world!");
- await message.SendReactionAsync("clap", score: 3);
+ await message.SendReactionAsync("clap", score: 5);
}
///
@@ -284,10 +367,90 @@ public async Task QuoteMessage()
}
///
- /// https://getstream.io/chat/docs/unity/reminders/?language=unity
+ /// https://getstream.io/chat/docs/unity/unread-reminders/?language=unity#enabling-unread-reminders
+ ///
+ public async Task EnableUnreadReminders()
+ {
+ // Enable in Dashboard: Open your application -> Channel Types -> Pick Channel Type -> Enable "Message Reminders"
+ await Task.CompletedTask;
+ }
+
+ ///
+ /// https://getstream.io/chat/docs/unity/message-reminders/?language=unity#enabling-reminders
///
- public async Task Reminders()
+ public async Task EnableMessageReminders()
{
+ // This is a server-side feature, choose any of our server-side SDKs (or the Stream Dashboard) to enable it
+ await Task.CompletedTask;
+ }
+
+ ///
+ /// https://getstream.io/chat/docs/unity/message-reminders/?language=unity#creating-a-message-reminder
+ ///
+ public async Task CreateMessageReminder()
+ {
+ // This feature is not yet available in the Unity SDK.
+ // Please let us know if you'd like this feature implemented: https://github.com/GetStream/stream-chat-unity/issues
+ await Task.CompletedTask;
+ }
+
+ ///
+ /// https://getstream.io/chat/docs/unity/message-reminders/?language=unity#updating-a-message-reminder
+ ///
+ public async Task UpdateMessageReminder()
+ {
+ // This feature is not yet available in the Unity SDK.
+ // Please let us know if you'd like this feature implemented: https://github.com/GetStream/stream-chat-unity/issues
+ await Task.CompletedTask;
+ }
+
+ ///
+ /// https://getstream.io/chat/docs/unity/message-reminders/?language=unity#deleting-a-message-reminder
+ ///
+ public async Task DeleteMessageReminder()
+ {
+ // This feature is not yet available in the Unity SDK.
+ // Please let us know if you'd like this feature implemented: https://github.com/GetStream/stream-chat-unity/issues
+ await Task.CompletedTask;
+ }
+
+ ///
+ /// https://getstream.io/chat/docs/unity/message-reminders/?language=unity#querying-message-reminders
+ ///
+ public async Task QueryMessageReminders()
+ {
+ // This feature is not yet available in the Unity SDK.
+ // Please let us know if you'd like this feature implemented: https://github.com/GetStream/stream-chat-unity/issues
+ await Task.CompletedTask;
+ }
+
+ ///
+ /// https://getstream.io/chat/docs/unity/message-reminders/?language=unity#filtering-reminders
+ ///
+ public async Task FilterMessageReminders()
+ {
+ // This feature is not yet available in the Unity SDK.
+ // Please let us know if you'd like this feature implemented: https://github.com/GetStream/stream-chat-unity/issues
+ await Task.CompletedTask;
+ }
+
+ ///
+ /// https://getstream.io/chat/docs/unity/message-reminders/?language=unity#pagination
+ ///
+ public async Task PaginateMessageReminders()
+ {
+ // This feature is not yet available in the Unity SDK.
+ // Please let us know if you'd like this feature implemented: https://github.com/GetStream/stream-chat-unity/issues
+ await Task.CompletedTask;
+ }
+
+ ///
+ /// https://getstream.io/chat/docs/unity/message-reminders/?language=unity#events
+ ///
+ public async Task MessageReminderEvents()
+ {
+ // This feature is not yet available in the Unity SDK.
+ // Please let us know if you'd like this feature implemented: https://github.com/GetStream/stream-chat-unity/issues
await Task.CompletedTask;
}
@@ -311,32 +474,43 @@ public async Task SilentMessages()
///
public async Task Search()
{
-// Access to low-level client is left for backward compatibility. Soon simplified syntax for searching will be implemented
- var searchResponse = await Client.LowLevelClient.MessageApi.SearchMessagesAsync(new SearchRequest
+ // Search for messages containing text
+ var results = await Client.SearchMessagesAsync(new StreamSearchMessagesRequest
{
- //Filter is required for search
- FilterConditions = new Dictionary
+ // Channel filter is required - here, channels the local user is a member of
+ ChannelFilter = new IFieldFilterRule[]
{
- {
- //Get channels that local user is a member of
- "members", new Dictionary
- {
- { "$in", new[] { "John" } }
- }
- }
+ ChannelFilter.Members.In("john"),
},
-
- //search phrase
- Query = "supercalifragilisticexpialidocious"
+ Query = "supercalifragilisticexpialidocious",
+ Limit = 10,
});
- foreach (var searchResult in searchResponse.Results)
+ foreach (var hit in results.Results)
{
- Debug.Log(searchResult.Message.Id); //Message ID
- Debug.Log(searchResult.Message.Text); //Message text
- Debug.Log(searchResult.Message.User); //Message author info
- Debug.Log(searchResult.Message.Channel); //Channel info
+ Debug.Log(hit.Message.Id); // Stateful IStreamMessage
+ Debug.Log(hit.Message.Text);
+ Debug.Log(hit.Message.User);
+ Debug.Log(hit.Channel.Cid); // Stateful IStreamChannel (auto-watched by default)
}
+
+ // Search with message filters - mutually exclusive with Query
+ var filtered = await Client.SearchMessagesAsync(new StreamSearchMessagesRequest
+ {
+ ChannelFilter = new IFieldFilterRule[]
+ {
+ ChannelFilter.Members.In("john"),
+ },
+ MessageFilter = new IFieldFilterRule[]
+ {
+ MessageFilter.Text.Autocomplete("super"),
+ MessageFilter.AttachmentType.In("image", "video"),
+ },
+ Limit = 10,
+ // Set to false for one-off search bars where you don't want every result
+ // channel to start receiving realtime updates.
+ WatchResultChannels = true,
+ });
}
///
@@ -344,64 +518,125 @@ public async Task Search()
///
public async Task SearchPagination()
{
- await Task.CompletedTask;
+ var channelFilters = new IFieldFilterRule[]
+ {
+ ChannelFilter.Cid.EqualsTo("messaging:my-channel"),
+ };
+ var messageFilters = new IFieldFilterRule[]
+ {
+ MessageFilter.Text.Autocomplete("supercali"),
+ };
+
+ // First page with custom sorting
+ var page1 = await Client.SearchMessagesAsync(new StreamSearchMessagesRequest
+ {
+ ChannelFilter = channelFilters,
+ MessageFilter = messageFilters,
+ Sort = MessagesSort
+ .OrderByDescending(MessageSortFieldName.Relevance)
+ .ThenByAscending(MessageSortFieldName.UpdatedAt),
+ Limit = 10,
+ });
+
+ // Next page using the cursor returned by the previous response
+ var page2 = await Client.SearchMessagesAsync(new StreamSearchMessagesRequest
+ {
+ ChannelFilter = channelFilters,
+ MessageFilter = messageFilters,
+ Limit = 10,
+ Next = page1.Next,
+ });
+
+ // Previous page
+ var page1Again = await Client.SearchMessagesAsync(new StreamSearchMessagesRequest
+ {
+ ChannelFilter = channelFilters,
+ MessageFilter = messageFilters,
+ Limit = 10,
+ Next = page2.Previous,
+ });
}
///
- /// https://getstream.io/chat/docs/unity/pinned_messages/?language=unity#pin-and-unpin-a-message
+ /// https://getstream.io/chat/docs/unity/pinned-messages/?language=unity#pinning-and-unpinning-messages
///
public async Task PinAndUnpinMessage()
{
- IStreamMessage message = null;
+ var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, channelId: "my-channel-id");
+ var message = await channel.SendNewMessageAsync("Important announcement");
-// Pin until unpinned
+ // Pin until unpinned
await message.PinAsync();
-// Pin for 7 days
- await message.PinAsync(new DateTime().AddDays(7));
+ // Pin for 7 days
+ await message.PinAsync(DateTime.UtcNow.AddDays(7));
-// Unpin previously pinned message
+ // Unpin previously pinned message
await message.UnpinAsync();
}
///
- /// https://getstream.io/chat/docs/unity/pinned_messages/?language=unity#retrieve-pinned-messages
+ /// https://getstream.io/chat/docs/unity/pinned-messages/?language=unity#retrieving-pinned-messages
///
public async Task RetrievePinnedMessages()
{
- await Task.CompletedTask;
+ var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, channelId: "my-channel-id");
+
+ // channel.PinnedMessages exposes the 10 most recent pinned messages loaded with the channel state.
+ foreach (var pinnedMessage in channel.PinnedMessages)
+ {
+ Debug.Log($"{pinnedMessage.Id}: {pinnedMessage.Text}");
+ }
}
///
- /// https://getstream.io/chat/docs/unity/pinned_messages/?language=unity#paginate-over-all-pinned-messages
+ /// https://getstream.io/chat/docs/unity/pinned-messages/?language=unity#paginating-pinned-messages
///
public async Task PaginatePinnedMessages()
{
+ // Paginating pinned messages via the dedicated pinned-messages endpoint is not yet available in the Unity SDK.
+ // Please let us know if you'd like this feature implemented: https://github.com/GetStream/stream-chat-unity/issues
+ // The channel.PinnedMessages snapshot loaded with the channel state (max 10) is the available alternative.
await Task.CompletedTask;
}
///
/// https://getstream.io/chat/docs/unity/translation/?language=unity#message-translation-endpoint
+ /// On-demand message translation is not yet exposed by the Unity SDK; the docs
+ /// surface the canonical "raise an issue" placeholder. Tracked in B5 of the docs
+ /// update plan.
///
- public async Task MessageTranslation()
+ public Task MessageTranslation()
{
- await Task.CompletedTask;
+ // This feature is not yet available in the Unity SDK.
+ // Please let us know if you'd like this feature implemented: https://github.com/GetStream/stream-chat-unity/issues
+ return Task.CompletedTask;
}
///
/// https://getstream.io/chat/docs/unity/translation/?language=unity#enabling-automatic-translation
+ /// Enabling automatic translation (per-channel or per-app) is server-side / not
+ /// yet exposed by the Unity stateful client; the docs surface the canonical
+ /// "raise an issue" placeholder. Tracked in B5 of the docs update plan.
///
- public async Task EnableAutomaticTranslation()
+ public Task EnableAutomaticTranslation()
{
- await Task.CompletedTask;
+ // This feature is not yet available in the Unity SDK.
+ // Please let us know if you'd like this feature implemented: https://github.com/GetStream/stream-chat-unity/issues
+ return Task.CompletedTask;
}
///
/// https://getstream.io/chat/docs/unity/translation/?language=unity#set-user-language
+ /// Setting the user language at connect time is not yet exposed by the Unity
+ /// stateful client; the docs surface the canonical "raise an issue" placeholder.
+ /// Tracked in B5 of the docs update plan.
///
- public async Task SetUserLanguage()
+ public Task SetUserLanguage()
{
- await Task.CompletedTask;
+ // This feature is not yet available in the Unity SDK.
+ // Please let us know if you'd like this feature implemented: https://github.com/GetStream/stream-chat-unity/issues
+ return Task.CompletedTask;
}
private IStreamChatClient Client { get; } = StreamChatClient.CreateDefaultClient();
diff --git a/Assets/Plugins/StreamChat/Samples/PollsCodeSamples.cs b/Assets/Plugins/StreamChat/Samples/PollsCodeSamples.cs
new file mode 100644
index 00000000..02df0c8a
--- /dev/null
+++ b/Assets/Plugins/StreamChat/Samples/PollsCodeSamples.cs
@@ -0,0 +1,324 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using StreamChat.Core;
+using StreamChat.Core.QueryBuilders.Filters;
+using StreamChat.Core.QueryBuilders.Filters.Polls;
+using StreamChat.Core.QueryBuilders.Sort;
+using StreamChat.Core.Requests;
+using StreamChat.Core.StatefulModels;
+
+namespace StreamChat.Samples
+{
+ internal sealed class PollsCodeSamples
+ {
+ ///
+ /// https://getstream.io/chat/docs/unity/polls-api/?language=unity#polls-at-a-quick-glance
+ ///
+ public async Task QuickGlance()
+ {
+ var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, "my-channel-id");
+
+ // Create a poll and send it in a message
+ var poll = await Client.Polls.CreatePollAsync(new StreamCreatePollRequest
+ {
+ Name = "Where should we host our next event?",
+ Options = new List
+ {
+ new StreamPollOptionRequest { Text = "Amsterdam" },
+ new StreamPollOptionRequest { Text = "Boulder" },
+ },
+ });
+ var message = await channel.SendNewMessageAsync(new StreamSendMessageRequest
+ {
+ Text = "Vote now!",
+ PollId = poll.Id,
+ });
+
+ // Vote on a poll
+ var optionId = poll.Options[0].Id;
+ await poll.CastVoteAsync(message.Id, optionId);
+
+ // Retrieve poll results
+ var refreshed = await Client.Polls.GetPollAsync(poll.Id);
+ var voteCountsByOption = refreshed.VoteCountsByOption; // { 'option-id': 5 }
+ }
+
+ ///
+ /// https://getstream.io/chat/docs/unity/polls-api/?language=unity#creating-a-poll-and-sending-it-as-part-of-a-message
+ ///
+ public async Task CreatePollWithMessage()
+ {
+ // Create a poll with options
+ var createPollRequest = new StreamCreatePollRequest
+ {
+ Name = "Where should we host our next company event?",
+ Options = new List
+ {
+ new StreamPollOptionRequest { Text = "Amsterdam, The Netherlands" },
+ new StreamPollOptionRequest { Text = "Boulder, CO" }
+ }
+ };
+
+ var poll = await Client.Polls.CreatePollAsync(createPollRequest);
+
+ // Get or create a channel
+ var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, "my-channel-id");
+
+ // Send a message with the poll
+ var messageRequest = new StreamSendMessageRequest
+ {
+ Text = "We want to know your opinion!",
+ PollId = poll.Id
+ };
+
+ var message = await channel.SendNewMessageAsync(messageRequest);
+
+ // message.PollId contains the poll ID
+ // Others users can use poll ID to fetch the poll object
+ var pollData = await Client.Polls.GetPollAsync(message.PollId);
+
+ // The poll object contains data, events, and operations:
+ // - Data: pollData.Name, pollData.Options, pollData.VoteCount, pollData.IsClosed, etc.
+ // - Events: pollData.Updated, pollData.Closed, pollData.VoteCasted, etc.
+ // - Operations: pollData.CastVoteAsync(), pollData.CloseAsync(), pollData.UpdateAsync(), etc.
+ // Explore the IStreamPoll interface for complete reference
+ }
+
+ ///
+ /// https://getstream.io/chat/docs/unity/polls-api/?language=unity#poll-options
+ /// Custom properties on a poll and on individual options.
+ ///
+ public async Task CreatePollWithCustomData()
+ {
+ var createPollRequest = new StreamCreatePollRequest
+ {
+ Name = "Where should we host our next company event?",
+ Options = new List
+ {
+ new StreamPollOptionRequest
+ {
+ Text = "Amsterdam, The Netherlands",
+ Custom = new Dictionary
+ {
+ { "venue_capacity", 300 }
+ }
+ },
+ new StreamPollOptionRequest
+ {
+ Text = "Boulder, CO",
+ Custom = new Dictionary
+ {
+ { "venue_capacity", 1000 }
+ }
+ }
+ },
+ Custom = new Dictionary
+ {
+ { "category", "company-events" },
+ { "priority", 5 }
+ }
+ };
+
+ var poll = await Client.Polls.CreatePollAsync(createPollRequest);
+ }
+
+ ///
+ /// https://getstream.io/chat/docs/unity/polls-api/?language=unity#send-vote-on-option
+ ///
+ public async Task CastVoteOnOption()
+ {
+ // Get the channel and message
+ var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, "my-channel-id");
+
+ // Assume we have a message with a poll
+ var message = channel.Messages.First(m => !string.IsNullOrEmpty(m.PollId));
+ var poll = await Client.Polls.GetPollAsync(message.PollId);
+
+ // Get an option to vote on
+ var option = poll.Options[0];
+
+ // Cast a vote on the option
+ await poll.CastVoteAsync(message.Id, option.Id);
+ }
+
+ ///
+ /// https://getstream.io/chat/docs/unity/polls-api/?language=unity#send-an-answer-(if-answers-are-configured-to-be-allowed)
+ /// Poll answers are not yet exposed by the Unity SDK; the docs show the
+ /// "raise an issue" placeholder. Tracked in F11 of the docs update plan.
+ ///
+ public Task CastAnswer()
+ {
+ // Poll answers are not yet available in the Unity SDK.
+ // Please let us know if you'd like this feature implemented: https://github.com/GetStream/stream-chat-unity/issues
+ return Task.CompletedTask;
+ }
+
+ ///
+ /// https://getstream.io/chat/docs/unity/polls-api/?language=unity#removing-a-vote
+ ///
+ public async Task RemoveVote()
+ {
+ // Get the channel and message
+ var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, "my-channel-id");
+
+ // Assume we have a message with a poll
+ var message = channel.Messages.First(m => !string.IsNullOrEmpty(m.PollId));
+ var poll = await Client.Polls.GetPollAsync(message.PollId);
+
+ // Get a vote to remove (e.g., from own votes)
+ var vote = poll.OwnVotes[0];
+
+ // Remove the vote
+ await poll.RemoveVoteAsync(message.Id, vote.Id);
+ }
+
+ ///
+ /// https://getstream.io/chat/docs/unity/polls-api/?language=unity#closing-a-poll
+ ///
+ public async Task ClosePoll()
+ {
+ // Get the poll reference
+ var poll = await Client.Polls.GetPollAsync("poll-id");
+
+ // Close the poll to prevent further votes
+ await poll.CloseAsync();
+ }
+
+ ///
+ /// https://getstream.io/chat/docs/unity/polls-api/?language=unity#retrieving-a-poll
+ ///
+ public async Task RetrievePoll()
+ {
+ // Retrieve a poll by ID
+ var poll = await Client.Polls.GetPollAsync("poll-id");
+
+ // Access poll properties
+ var name = poll.Name;
+ var description = poll.Description;
+ var options = poll.Options;
+ var voteCount = poll.VoteCount;
+ var isClosed = poll.IsClosed;
+ }
+
+ ///
+ /// https://getstream.io/chat/docs/unity/polls-api/?language=unity#full-update
+ ///
+ public async Task UpdatePollFull()
+ {
+ // Get the poll reference
+ var poll = await Client.Polls.GetPollAsync("poll-id");
+
+ // Update the poll (full update)
+ var updateRequest = new StreamUpdatePollRequest
+ {
+ Name = "Where should we not go to?",
+ Description = "Updated description"
+ };
+
+ await poll.UpdateAsync(updateRequest);
+ }
+
+ ///
+ /// https://getstream.io/chat/docs/unity/polls-api/?language=unity#partial-update
+ ///
+ public async Task UpdatePollPartial()
+ {
+ // Get the poll reference
+ var poll = await Client.Polls.GetPollAsync("poll-id");
+
+ // Partial update - set specific fields and/or unset fields
+ await poll.UpdatePartialAsync(
+ setFields: new Dictionary
+ {
+ { "name", "Where should we not go to?" },
+ { "max_votes_allowed", 3 }
+ },
+ unsetFields: new List { "custom_property" });
+ }
+
+ ///
+ /// https://getstream.io/chat/docs/unity/polls-api/?language=unity#deleting-a-poll
+ ///
+ public async Task DeletePoll()
+ {
+ // Delete a poll by ID through the Polls API
+ await Client.Polls.DeletePollAsync("poll-id");
+ }
+
+ ///
+ /// https://getstream.io/chat/docs/unity/polls-api/?language=unity#add-poll-option
+ ///
+ public async Task AddPollOption()
+ {
+ // Get the poll reference
+ var poll = await Client.Polls.GetPollAsync("poll-id");
+
+ // Add a new option - takes a text string parameter
+ var newOption = await poll.AddOptionAsync("Another option");
+ }
+
+ ///
+ /// https://getstream.io/chat/docs/unity/polls-api/?language=unity#update-poll-option
+ ///
+ public async Task UpdatePollOption()
+ {
+ // Get the poll reference
+ var poll = await Client.Polls.GetPollAsync("poll-id");
+
+ // Get an option to update
+ var option = poll.Options[0];
+
+ // Update the option - takes optionId and text parameters
+ var updatedOption = await poll.UpdateOptionAsync(option.Id, "Updated option");
+ }
+
+ ///
+ /// https://getstream.io/chat/docs/unity/polls-api/?language=unity#delete-poll-option
+ ///
+ public async Task DeletePollOption()
+ {
+ // Get the poll reference
+ var poll = await Client.Polls.GetPollAsync("poll-id");
+
+ // Get an option to delete
+ var option = poll.Options[0];
+
+ // Delete the option
+ await poll.DeleteOptionAsync(option.Id);
+ }
+
+ ///
+ /// https://getstream.io/chat/docs/unity/polls-api/?language=unity#querying-votes
+ /// Querying poll votes is not yet exposed by the Unity SDK. The docs
+ /// surface this placeholder; tracked in F11 of the docs update plan.
+ ///
+ public Task QueryPollVotes()
+ {
+ // Querying poll votes is not yet available in the Unity SDK.
+ // You can access latest votes through poll.LatestVotesByOption and poll.OwnVotes properties.
+ // Please let us know if you'd like full vote querying implemented: https://github.com/GetStream/stream-chat-unity/issues
+ return Task.CompletedTask;
+ }
+
+ ///
+ /// https://getstream.io/chat/docs/unity/polls-api/?language=unity#querying-polls
+ ///
+ public async Task QueryPolls()
+ {
+ // Retrieve all polls that are closed for voting, sorted by created_at
+ var queryRequest = new StreamQueryPollsRequest
+ {
+ Filter = new IFieldFilterRule[]
+ {
+ PollFilter.IsClosed.EqualsTo(true)
+ },
+ Sort = PollSort.OrderByDescending(PollSortFieldName.CreatedAt)
+ };
+
+ var polls = await Client.Polls.QueryPollsAsync(queryRequest);
+ }
+
+ private IStreamChatClient Client { get; } = StreamChatClient.CreateDefaultClient();
+ }
+}
diff --git a/Assets/Plugins/StreamChat/Samples/PollsCodeSamples.cs.meta b/Assets/Plugins/StreamChat/Samples/PollsCodeSamples.cs.meta
new file mode 100644
index 00000000..73aec564
--- /dev/null
+++ b/Assets/Plugins/StreamChat/Samples/PollsCodeSamples.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 7b1c3e8d4a6f4f7da1e0d2f8a9c5b1e2
+timeCreated: 1748966400
diff --git a/Assets/Plugins/StreamChat/Samples/RateLimitsCodeSamples.cs b/Assets/Plugins/StreamChat/Samples/RateLimitsCodeSamples.cs
new file mode 100644
index 00000000..d05f8799
--- /dev/null
+++ b/Assets/Plugins/StreamChat/Samples/RateLimitsCodeSamples.cs
@@ -0,0 +1,85 @@
+using System;
+using System.Threading.Tasks;
+using StreamChat.Core;
+using StreamChat.Core.Exceptions;
+using StreamChat.Core.StatefulModels;
+using UnityEngine;
+
+namespace StreamChat.Samples
+{
+ internal sealed class RateLimitsCodeSamples
+ {
+ ///
+ /// https://getstream.io/chat/docs/unity/rate-limits/?language=unity#catching-429s-in-backend-sdks
+ ///
+ public async Task CatchingApiErrors()
+ {
+ IStreamChannel channel = null;
+
+ // Example 1 — single catch + switch on the numeric error code.
+ try
+ {
+ await channel.SendNewMessageAsync("Hello");
+ }
+ catch (StreamApiException ex)
+ {
+ // ex.Code / ex.StatusCode / ex.ErrorMessage / ex.ExceptionFields / ex.MoreInfo
+ // are all available for inspection. See the API Error Codes docs page
+ // for the full list.
+ switch (ex.Code)
+ {
+ case StreamApiException.RateLimitErrorStreamCode:
+ // HTTP 429 — back off with exponential delay before retrying.
+ await Task.Delay(TimeSpan.FromSeconds(1));
+ break;
+ case StreamApiException.CooldownErrorStreamCode:
+ // HTTP 403 — slow-mode cooldown. Gate the send UI for `channel.Cooldown` seconds.
+ break;
+ case StreamApiException.PermissionDeniedErrorStreamCode:
+ // HTTP 403 — user lacks permission. Hide / disable the control.
+ break;
+ default:
+ Debug.LogError($"Stream API error {ex.Code} (HTTP {ex.StatusCode}): {ex.ErrorMessage}");
+ break;
+ }
+ }
+
+ // Alternatively, use the `when` syntax combined with our dedicated
+ // error-checking extensions.
+ try
+ {
+ await channel.SendNewMessageAsync("Hello");
+ }
+ catch (StreamApiException ex) when (ex.IsRateLimitExceededError())
+ {
+ // HTTP 429 / Stream code 9 — back off before retrying.
+ await Task.Delay(TimeSpan.FromSeconds(1));
+ }
+
+ // Most common error cases have a dedicated extension method on StreamApiException.
+ // Pick the helper that matches your branch instead of comparing `ex.Code` by hand:
+ // IsRateLimitExceededError (429 / 9) — back off and retry
+ // IsCooldownError (403 / 60) — gate UI for `channel.Cooldown` seconds
+ // IsPermissionDeniedError (403 / 17) — hide the UI control
+ // IsNoAccessToChannelsError (403 / 70) — drop the channel locally
+ // IsAppSuspendedError (403 / 99) — show service-unavailable UI
+ // IsAuthenticationError (401 / 5) — send the user back to sign-in
+ // IsTokenExpiredError (401 / 40) — refresh the token
+ // IsTokenError (401 / 40-43) — any token-related failure
+ // IsDoesNotExistError (404 / 16) — refresh the local view
+ // IsInputError (400 / 4) — inspect `ex.ExceptionFields`
+ // IsMessageTooLongError (400 / 20) — show character-limit error
+ // IsMessageModerationFailedError (400 / 73) — show "filtered" UI
+ // IsPayloadTooBigError (413 / 22) — ask for a smaller file
+ // IsInternalSystemError (500 / -1) — retry, then surface a transient-failure UI
+ //
+ // The general guidance: wrap a call in try/catch where you want to react
+ // specifically (rate-limit back-off, cooldown UI, validation feedback,
+ // permission gating). For everything else, let the exception propagate to
+ // your global error handler — every SDK call already throws StreamApiException
+ // on server-rejected requests, so a single boundary catch is enough.
+ }
+
+ private IStreamChatClient Client { get; } = StreamChatClient.CreateDefaultClient();
+ }
+}
diff --git a/Assets/Plugins/StreamChat/Samples/RateLimitsCodeSamples.cs.meta b/Assets/Plugins/StreamChat/Samples/RateLimitsCodeSamples.cs.meta
new file mode 100644
index 00000000..7cc70a53
--- /dev/null
+++ b/Assets/Plugins/StreamChat/Samples/RateLimitsCodeSamples.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 3ff4fd277f0449ce87f3982fa0659cec
+timeCreated: 1749567000
diff --git a/Assets/Plugins/StreamChat/Samples/ThreadsCodeSamples.cs b/Assets/Plugins/StreamChat/Samples/ThreadsCodeSamples.cs
index 307a5018..15a720c2 100644
--- a/Assets/Plugins/StreamChat/Samples/ThreadsCodeSamples.cs
+++ b/Assets/Plugins/StreamChat/Samples/ThreadsCodeSamples.cs
@@ -1,3 +1,4 @@
+using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using StreamChat.Core;
@@ -6,144 +7,224 @@
using StreamChat.Core.QueryBuilders.Sort;
using StreamChat.Core.Requests;
using StreamChat.Core.StatefulModels;
+using UnityEngine;
namespace StreamChat.Samples
{
internal sealed class ThreadsCodeSamples
{
///
- /// https://getstream.io/chat/docs/unity/threads/?language=unity
+ /// https://getstream.io/chat/docs/unity/threads/?language=unity#starting-a-thread
///
public async Task SendReply()
{
var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, "my-channel-id");
- var parentMessage = await channel.SendNewMessageAsync("Let's start a thread!");
+ var parentMessage = await channel.SendNewMessageAsync("Starting a thread");
- // Reply in the thread (won't appear in main channel timeline)
var reply = await channel.SendNewMessageAsync(new StreamSendMessageRequest
{
ParentId = parentMessage.Id,
ShowInChannel = false,
- Text = "Hello!",
+ Text = "This is a reply in a thread",
});
}
///
- /// https://getstream.io/chat/docs/unity/threads/?language=unity#load-replies
+ /// https://getstream.io/chat/docs/unity/threads/?language=unity#paginating-thread-replies
///
public async Task LoadReplies()
{
var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, "my-channel-id");
+ var parentMessage = await channel.SendNewMessageAsync("Starting a thread");
- var parentMessage = await channel.SendNewMessageAsync("Let's start a thread!");
+ // Get the latest 20 replies (oldest-first)
+ var replies = await parentMessage.LoadRepliesAsync(limit: 20);
- // Load the most recent replies of a thread (oldest-first)
- var firstPage = await parentMessage.LoadRepliesAsync(limit: 25);
+ // Get older replies (before message with id "42")
+ var olderReplies = await parentMessage.LoadRepliesAsync(limit: 20, idLessThan: "42");
+ }
+
+ ///
+ /// https://getstream.io/chat/docs/unity/threads/?language=unity#inline-replies
+ ///
+ public async Task InlineReply()
+ {
+ var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, "my-channel-id");
+ var originalMessage = await channel.SendNewMessageAsync("Original message");
- // Load older replies using id_lt pagination
- if (firstPage.Count > 0)
+ var message = await channel.SendNewMessageAsync(new StreamSendMessageRequest
{
- var olderPage = await parentMessage.LoadRepliesAsync(limit: 25, idLessThan: firstPage[0].Id);
- }
+ QuotedMessage = originalMessage,
+ Text = "I agree with this point",
+ });
}
///
- /// https://getstream.io/chat/docs/unity/threads/?language=unity#get-thread
+ /// https://getstream.io/chat/docs/unity/threads/?language=unity#querying-threads
///
- public async Task GetThread()
+ public async Task QueryThreads()
{
- // Fetch a thread by its parent message id
- var thread = await Client.GetThreadAsync("parent-message-id", replyLimit: 25, participantLimit: 25);
-
- var participants = thread.ThreadParticipants;
- var replies = thread.LatestReplies;
- var replyCount = thread.ReplyCount;
+ var response = await Client.QueryThreadsAsync(new StreamQueryThreadsRequest
+ {
+ Watch = true,
+ Limit = 10,
+ });
- // Subscribe to thread updates
- thread.Updated += changedThread => { /* handle change */ };
- thread.ReplyReceived += (changedThread, reply) => { /* handle new reply */ };
- thread.ReadStateChanged += changedThread => { /* unread state changed */ };
+ foreach (var thread in response.Threads)
+ {
+ // Threads are cached, watched and kept in sync with realtime events
+ Debug.Log(thread.ParentMessage.Text);
+ Debug.Log(thread.LatestReplies);
+ Debug.Log(thread.ThreadParticipants);
+ Debug.Log(thread.Read);
+ }
}
///
- /// https://getstream.io/chat/docs/unity/threads/?language=unity#query-threads
+ /// https://getstream.io/chat/docs/unity/threads/?language=unity#filtering-and-sorting
///
- public async Task QueryThreads()
+ public async Task QueryThreadsWithFilterAndSort()
{
+ var since = new DateTimeOffset(2024, 1, 1, 0, 0, 0, TimeSpan.Zero);
+
var request = new StreamQueryThreadsRequest
{
- Limit = 20,
- ReplyLimit = 5,
- ParticipantLimit = 5,
- Watch = true,
Filter = new IFieldFilterRule[]
{
- ThreadFilter.ChannelCid.EqualsTo("messaging:my-channel-id"),
+ ThreadFilter.CreatedByUserId.EqualsTo("user-1"),
+ ThreadFilter.UpdatedAt.GreaterThanOrEquals(since),
},
- Sort = ThreadSort.OrderByDescending(ThreadSortFieldName.LastMessageAt),
+ Sort = ThreadSort.OrderByDescending(ThreadSortFieldName.CreatedAt),
+ Limit = 10,
};
- var response = await Client.QueryThreadsAsync(request);
+ var page1 = await Client.QueryThreadsAsync(request);
- foreach (var thread in response.Threads)
+ // Get next page using the cursor returned by the previous response
+ if (!string.IsNullOrEmpty(page1.Next))
{
- // Use thread state. The thread is already cached and gets WS updates.
- var title = thread.Title;
- var replyCount = thread.ReplyCount;
+ request.Next = page1.Next;
+ var page2 = await Client.QueryThreadsAsync(request);
}
+ }
- // Pagination
- if (!string.IsNullOrEmpty(response.Next))
- {
- request.Next = response.Next;
- var nextPage = await Client.QueryThreadsAsync(request);
- }
+ ///
+ /// https://getstream.io/chat/docs/unity/threads/?language=unity#getting-a-thread-by-id
+ ///
+ public async Task GetThread()
+ {
+ // The returned IStreamThread is auto-watched (watch defaults to true) and stays in
+ // sync with realtime events via Updated / ReplyReceived / ReadStateChanged.
+ var thread = await Client.GetThreadAsync("parent-message-id",
+ replyLimit: 10, participantLimit: 25);
+
+ var participants = thread.ThreadParticipants;
+ var replies = thread.LatestReplies;
+ var replyCount = thread.ReplyCount;
+
+ thread.Updated += changedThread => { /* title or custom data changed */ };
+ thread.ReplyReceived += (changedThread, reply) => { /* new reply arrived */ };
+ thread.ReadStateChanged += changedThread => { /* unread state changed */ };
}
///
- /// https://getstream.io/chat/docs/unity/threads/?language=unity#partial-update
+ /// https://getstream.io/chat/docs/unity/threads/?language=unity#updating-thread-title-and-custom-data
///
public async Task UpdateThread()
{
var thread = await Client.GetThreadAsync("parent-message-id");
+ // Set title and custom fields; unset a previously set field
await thread.UpdatePartialAsync(
setFields: new Dictionary
{
- { "title", "Updated thread title" },
- { "my_custom_field", 42 },
+ { "title", "Project Discussion" },
+ { "priority", "high" },
},
- unsetFields: new[] { "obsolete_field" });
+ unsetFields: new[] { "priority" });
}
///
- /// https://getstream.io/chat/docs/unity/threads/?language=unity#mark-as-read
+ /// https://getstream.io/chat/docs/unity/threads/?language=unity#total-unread-threads
+ ///
+ public async Task ObserveTotalUnreadThreads()
+ {
+ // Available immediately after connect on IStreamLocalUserData
+ var unreadThreads = Client.LocalUserData.UnreadThreads;
+ Debug.Log(unreadThreads);
+
+ // The same total is also available on demand from the server
+ var unreadCounts = await Client.GetLatestUnreadCountsAsync();
+ Debug.Log(unreadCounts.TotalUnreadThreadsCount);
+ }
+
+ ///
+ /// https://getstream.io/chat/docs/unity/threads/?language=unity#marking-threads-as-read-or-unread
///
public async Task MarkThreadReadAndUnread()
{
var thread = await Client.GetThreadAsync("parent-message-id");
- // Mark this thread as read
+ // Mark this thread as read for the local user
await thread.MarkReadAsync();
// Mark this thread as unread starting from the parent message
await thread.MarkUnreadAsync();
- // From a message reference (the parent)
+ // Equivalent helpers from the parent message of the thread
IStreamMessage parentMessage = thread.ParentMessage;
await parentMessage.MarkThreadAsReadAsync();
await parentMessage.MarkThreadAsUnreadAsync();
+
+ // Or by parent message id when you already have the channel
+ await thread.Channel.MarkThreadAsReadAsync(thread.ParentMessageId);
+ await thread.Channel.MarkThreadAsUnreadAsync(thread.ParentMessageId);
+ }
+
+ ///
+ /// https://getstream.io/chat/docs/unity/threads/?language=unity#unread-count-per-thread
+ ///
+ public async Task UnreadCountPerThread()
+ {
+ var unreadCounts = await Client.GetLatestUnreadCountsAsync();
+
+ Debug.Log(unreadCounts.TotalUnreadThreadsCount);
+
+ foreach (var thread in unreadCounts.UnreadThreads)
+ {
+ Debug.Log(thread.ParentMessageId);
+ Debug.Log(thread.UnreadCount);
+ Debug.Log(thread.LastRead);
+ Debug.Log(thread.LastReadMessageId);
+ }
}
///
- /// https://getstream.io/chat/docs/unity/threads/?language=unity#unread-count
+ /// https://getstream.io/chat/docs/unity/threads/?language=unity#thread-manager
///
- public void ObserveUnreadThreadsCount()
+ public async Task TrackThreadsAndStayInSync()
{
- var localUserData = Client.LocalUserData;
+ // Get notified when a thread starts or stops being tracked locally
+ Client.ThreadTracked += thread => { /* a new IStreamThread is now tracked */ };
+ Client.ThreadUntracked += thread => { /* an IStreamThread is no longer tracked */ };
- var unreadThreads = localUserData.UnreadThreads;
+ // Load threads. Watch defaults to true so realtime updates are delivered.
+ var response = await Client.QueryThreadsAsync(new StreamQueryThreadsRequest
+ {
+ Watch = true,
+ Limit = 10,
+ });
+
+ // Each returned IStreamThread is stateful: it is cached and kept in sync with
+ // realtime events automatically. Subscribe to per-thread events to react to
+ // changes (e.g. new replies, title / custom data updates, read state changes).
+ foreach (var thread in response.Threads)
+ {
+ thread.Updated += changedThread => { /* title or custom data changed */ };
+ thread.ReplyReceived += (changedThread, reply) => { /* new reply arrived */ };
+ thread.ReadStateChanged += changedThread => { /* unread state changed */ };
+ }
}
public IStreamChatClient Client { get; private set; }
diff --git a/Assets/Plugins/StreamChat/Samples/TokensAndAuthentication.cs b/Assets/Plugins/StreamChat/Samples/TokensAndAuthentication.cs
deleted file mode 100644
index 4f536736..00000000
--- a/Assets/Plugins/StreamChat/Samples/TokensAndAuthentication.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-using System;
-using System.Threading.Tasks;
-using StreamChat.Core;
-using StreamChat.Libs.Auth;
-
-namespace StreamChat.Samples
-{
- internal sealed class TokensAndAuthentication
- {
- ///
- /// https://getstream.io/chat/docs/unity/tokens_and_authentication/?language=unity#how-to-refresh-expired-tokens
- ///
- public async Task ConnectWithTokenProvider()
- {
- // If your backend exposes a simple endpoint to get token from a url you can use our predefined token provider and provide delegate for URL construction
- var tokenProvider = StreamChatClient.CreateDefaultTokenProvider(userId
- => new Uri($"https:your-awesome-page.com/api/get_token?userId={userId}"));
- await Client.ConnectUserAsync("api-key", "local-user-id", tokenProvider);
-
-
- // For more advanced cases you can implement the ITokenProvider
- var yourTokenProvider = new YourTokenProvider();
- await Client.ConnectUserAsync("api-key", "local-user-id", yourTokenProvider);
- }
-
-// You can write your own implementation of the ITokenProvider
- public class YourTokenProvider : ITokenProvider
- {
- public async Task GetTokenAsync(string userId)
- {
- // Your logic to get the auth token from your backend and generated with Stream backend SDK
- var token = await Task.FromResult("some-token");
- return token;
- }
- }
-
- private IStreamChatClient Client { get; } = StreamChatClient.CreateDefaultClient();
- }
-}
\ No newline at end of file
diff --git a/Assets/Plugins/StreamChat/Samples/TokensAndAuthentication.cs.meta b/Assets/Plugins/StreamChat/Samples/TokensAndAuthentication.cs.meta
deleted file mode 100644
index 5c2f7ac9..00000000
--- a/Assets/Plugins/StreamChat/Samples/TokensAndAuthentication.cs.meta
+++ /dev/null
@@ -1,3 +0,0 @@
-fileFormatVersion: 2
-guid: 3d248a08391e4421a4af35441a53e232
-timeCreated: 1671659645
\ No newline at end of file
diff --git a/Assets/Plugins/StreamChat/Samples/UnreadCountsCodeSamples.cs b/Assets/Plugins/StreamChat/Samples/UnreadCountsCodeSamples.cs
index e15f9276..a7f3c4c6 100644
--- a/Assets/Plugins/StreamChat/Samples/UnreadCountsCodeSamples.cs
+++ b/Assets/Plugins/StreamChat/Samples/UnreadCountsCodeSamples.cs
@@ -9,30 +9,61 @@ namespace StreamChat.Samples
internal sealed class UnreadCountsCodeSamples
{
///
- /// https://getstream.io/chat/docs/unity/unread/?language=unity
+ /// https://getstream.io/chat/docs/unity/unread/?language=unity#reading-unread-counts
///
- ///
- public async Task GetUnreadCounts()
+ public async Task ReadingUnreadCounts()
{
- // Once user is connected you can access unread counts via IStreamLocalUserData
- var localUserData = Client.LocalUserData;
+ // Step 1: Get initial unread counts when connecting
+ var localUserData = await Client.ConnectUserAsync("api_key", "user_id", "user_token");
Debug.Log(localUserData.UnreadChannels);
Debug.Log(localUserData.TotalUnreadCount);
- // It's also returned by the ConnectUserAsync method
- var localUserData2 = await Client.ConnectUserAsync("api_key", "user_id", "user_token");
-
- // And also returned by the Connected event
- Client.Connected += ClientOnConnected;
-
- // All above examples returned the same IStreamLocalUserData object which represents the local user connected to the Stream Chat server
+ // You can also access unread counts via Client.LocalUserData after connection
+ // Or subscribe to the Connected event for real-time updates
+ Client.Connected += (IStreamLocalUserData userData) =>
+ {
+ Debug.Log(userData.UnreadChannels);
+ Debug.Log(userData.TotalUnreadCount);
+ };
}
- private void ClientOnConnected(IStreamLocalUserData localUserData)
+ ///
+ /// https://getstream.io/chat/docs/unity/unread/?language=unity#unread-counts---server-side
+ ///
+ public async Task GetLatestUnreadCounts()
{
+ var current = await Client.GetLatestUnreadCountsAsync();
+
+ Debug.Log(current.TotalUnreadCount); // Total unread messages
+ Debug.Log(current.TotalUnreadThreadsCount); // Total unread threads
+
+ foreach (var unreadChannel in current.UnreadChannels)
+ {
+ Debug.Log(unreadChannel.ChannelCid); // CID of the channel with unread messages
+ Debug.Log(unreadChannel.UnreadCount); // Count of unread messages
+ Debug.Log(unreadChannel.LastRead); // Datetime of the last read message
+ }
+
+ foreach (var unreadChannelByType in current.UnreadChannelsByType)
+ {
+ Debug.Log(unreadChannelByType.ChannelType); // Channel type
+ Debug.Log(unreadChannelByType.ChannelCount); // How many channels of this type have unread messages
+ Debug.Log(unreadChannelByType.UnreadCount); // How many unread messages in all channels of this type
+ }
+
+ foreach (var unreadThread in current.UnreadThreads)
+ {
+ Debug.Log(unreadThread.ParentMessageId); // Message ID of the parent message for this thread
+ Debug.Log(unreadThread.LastReadMessageId); // Last read message in this thread
+ Debug.Log(unreadThread.UnreadCount); // Count of unread messages
+ Debug.Log(unreadThread.LastRead); // Datetime of the last read message
+ }
}
+ ///
+ /// https://getstream.io/chat/docs/unity/unread/?language=unity#mark-read
+ ///
public async Task MarkRead()
{
IStreamMessage message = null;
@@ -40,28 +71,29 @@ public async Task MarkRead()
await message.MarkMessageAsLastReadAsync();
}
- public async Task ObserveReadState()
- {
- await Task.CompletedTask;
- }
-
- public void ChannelsReadState()
+ ///
+ /// https://getstream.io/chat/docs/unity/unread/?language=unity#mark-read
+ ///
+ public async Task MarkAlreadyReadMessageAsUnread()
{
+ IStreamMessage message = null;
IStreamChannel channel = null;
+ var messageId = "message-id";
- // Every channel maintains a full list of read state for each channel member
- foreach (var read in channel.Read)
- {
- Debug.Log(read.User); // User
- Debug.Log(read.UnreadMessages); // How many unread messages
- Debug.Log(read.LastRead); // Last read date
- }
+ // Mark the channel containing this message as unread starting from this message
+ await message.MarkAsUnreadAsync();
+
+ // Or mark a channel as unread starting from a specific message id
+ await channel.MarkChannelAsUnreadAsync(messageId);
}
- public async Task MarkRead2()
+ ///
+ /// https://getstream.io/chat/docs/unity/unread/?language=unity#mark-all-as-read
+ ///
+ public async Task MarkAllAsRead()
{
- IStreamChannel channel = null;
IStreamMessage message = null;
+ IStreamChannel channel = null;
// Mark this message as last read
await message.MarkMessageAsLastReadAsync();
@@ -70,36 +102,52 @@ public async Task MarkRead2()
await channel.MarkChannelReadAsync();
}
- public async Task GetCurrentUnreadCounts()
+ ///
+ /// https://getstream.io/chat/docs/unity/unread/?language=unity#read-state---showing-how-far-other-users-have-read
+ ///
+ public void ReadState()
{
- var current = await Client.GetLatestUnreadCountsAsync();
-
- Debug.Log(current.TotalUnreadCount); // Total unread messages
- Debug.Log(current.TotalUnreadThreadsCount); // Total unread threads
+ IStreamChannel channel = null;
- foreach (var unreadChannel in current.UnreadChannels)
+ // Every channel maintains a full list of read state for each channel member
+ foreach (var read in channel.Read)
{
- Debug.Log(unreadChannel.ChannelCid); // CID of the channel with unread messages
- Debug.Log(unreadChannel.UnreadCount); // Count of unread messages
- Debug.Log(unreadChannel.LastRead); // Datetime of the last read message
+ Debug.Log(read.User); // User
+ Debug.Log(read.UnreadMessages); // How many unread messages
+ Debug.Log(read.LastRead); // Last read date
}
+ }
- foreach (var unreadChannelByType in current.UnreadChannelsByType)
- {
- Debug.Log(unreadChannelByType.ChannelType); // Channel type
- Debug.Log(unreadChannelByType.ChannelCount); // How many channels of this type have unread messages
- Debug.Log(unreadChannelByType.UnreadCount); // How many unread messages in all channels of this type
- }
+ ///
+ /// https://getstream.io/chat/docs/unity/unread/?language=unity#unread-messages-per-channel
+ ///
+ public void UnreadMessagesPerChannel()
+ {
+ IStreamChannel channel = null;
- foreach (var unreadThread in current.UnreadThreads)
+ // Every channel maintains a full list of read state for each channel member
+ foreach (var read in channel.Read)
{
- Debug.Log(unreadThread.ParentMessageId); // Message ID of the parent message for this thread
- Debug.Log(unreadThread.LastReadMessageId); // Last read message in this thread
- Debug.Log(unreadThread.UnreadCount); // Count of unread messages
- Debug.Log(unreadThread.LastRead); // Datetime of the last read message
+ Debug.Log(read.User); // User
+ Debug.Log(read.UnreadMessages); // How many unread messages
+ Debug.Log(read.LastRead); // Last read date
}
}
-
+
+ ///
+ /// https://getstream.io/chat/docs/unity/unread/?language=unity#unread-mentions-per-channel
+ ///
+ public Task UnreadMentionsPerChannel()
+ {
+ // This feature is not yet available in the Unity SDK.
+ // Please let us know if you'd like this feature implemented: https://github.com/GetStream/stream-chat-unity/issues
+ return Task.CompletedTask;
+ }
+
+ ///
+ /// Retrieve unread counts without first calling .
+ /// This is useful for surfacing an unread badge in the background without a persistent connection.
+ ///
public async Task GetLatestUnreadCountsInOfflineMode()
{
// Set authorization credentials
@@ -112,4 +160,4 @@ public async Task GetLatestUnreadCountsInOfflineMode()
private IStreamChatClient Client { get; } = StreamChatClient.CreateDefaultClient();
}
-}
\ No newline at end of file
+}
diff --git a/Assets/Plugins/StreamChat/Samples/UserPresenceCodeExamples.cs b/Assets/Plugins/StreamChat/Samples/UserPresenceCodeExamples.cs
index 0b8aada5..fa2780dd 100644
--- a/Assets/Plugins/StreamChat/Samples/UserPresenceCodeExamples.cs
+++ b/Assets/Plugins/StreamChat/Samples/UserPresenceCodeExamples.cs
@@ -51,7 +51,8 @@ public void SetVisibilityOnConnect()
{
//StreamTodo: implement https://getstream.io/chat/docs/unity/presence_format/?language=csharp#invisible (second example)
- // Will be implemented soon, please send a support ticket if you need this feature
+ // This feature is not yet available in the Unity SDK.
+ // Please let us know if you'd like this feature implemented: https://github.com/GetStream/stream-chat-unity/issues
}
public async void ListeningForPresenceEvents()