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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using StreamChat.Core.InternalDTO.Models;

namespace StreamChat.Core.InternalDTO.Events
{
// Hand-written (not OpenAPI-generated). As of today the OpenAPI spec CustomEvent schema is invalid
// and missing key fields required for WS dispatch (cid, user, channel_type, channel_id, parent_id).
internal sealed class CustomEventInternalDTO
{
[Newtonsoft.Json.JsonProperty("type")]
public string Type { get; set; }

[Newtonsoft.Json.JsonProperty("cid")]
public string Cid { get; set; }

[Newtonsoft.Json.JsonProperty("channel_type")]
public string ChannelType { get; set; }

[Newtonsoft.Json.JsonProperty("channel_id")]
public string ChannelId { get; set; }

[Newtonsoft.Json.JsonProperty("parent_id")]
public string ParentId { get; set; }

[Newtonsoft.Json.JsonProperty("created_at")]
public DateTimeOffset CreatedAt { get; set; }

[Newtonsoft.Json.JsonProperty("user")]
public UserObjectInternalDTO User { get; set; }

[Newtonsoft.Json.JsonExtensionData]
public Dictionary<string, object> AdditionalProperties { get; set; }
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using StreamChat.Core.Helpers;
using StreamChat.Core.InternalDTO.Responses;
Expand Down Expand Up @@ -142,6 +143,10 @@ public Task SendTypingStartEventAsync(string channelType, string channelId)
public Task SendTypingStopEventAsync(string channelType, string channelId)
=> _internalChannelApi.SendTypingStopEventAsync(channelType, channelId);

public Task SendCustomEventAsync(string channelType, string channelId, string eventType,
IDictionary<string, object> customData = null)
=> _internalChannelApi.SendCustomEventAsync(channelType, channelId, eventType, customData);

public async Task<SyncResponse> SyncAsync(SyncRequest syncRequest)
{
var dto = await _internalChannelApi.SyncAsync(syncRequest.TrySaveToDto());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using StreamChat.Core.LowLevelClient.Models;
using StreamChat.Core.LowLevelClient.Requests;
Expand Down Expand Up @@ -151,6 +152,13 @@ Task<MarkReadResponse> MarkReadAsync(string channelType, string channelId,

Task SendTypingStopEventAsync(string channelType, string channelId);

/// <summary>
/// Send a custom event to a channel. All members currently watching the channel receive it over the websocket.
/// </summary>
/// <remarks>https://getstream.io/chat/docs/unity/event_object/?language=unity#custom-events</remarks>
Task SendCustomEventAsync(string channelType, string channelId, string eventType,
IDictionary<string, object> customData = null);

//StreamTodo: perhaps we can skip this declaration and use the Internal one directly
Task<SyncResponse> SyncAsync(SyncRequest syncRequest);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Threading.Tasks;
using StreamChat.Core.InternalDTO.Requests;
using StreamChat.Core.InternalDTO.Responses;

Expand Down Expand Up @@ -56,6 +57,9 @@ Task<ResponseInternalDTO> MarkUnreadAsync(string channelType, string channelId,

Task SendTypingStopEventAsync(string channelType, string channelId);

Task SendCustomEventAsync(string channelType, string channelId, string eventType,
IDictionary<string, object> customData);

Task<SyncResponseInternalDTO> SyncAsync(SyncRequestInternalDTO syncRequest);

Task<WrappedUnreadCountsResponseInternalDTO> GetUnreadCountsAsync();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using StreamChat.Core.InternalDTO.Events;
using StreamChat.Core.InternalDTO.Requests;
Expand Down Expand Up @@ -139,6 +141,22 @@ public Task SendTypingStopEventAsync(string channelType, string channelId)
Type = WSEventType.TypingStop
});

public Task SendCustomEventAsync(string channelType, string channelId, string eventType,
IDictionary<string, object> customData)
{
var eventBody = new EventRequestInternalDTO
{
Type = eventType,
};

if (customData != null && customData.Count > 0)
{
eventBody.AdditionalProperties = customData.ToDictionary(kv => kv.Key, kv => kv.Value);
}

return PostEventAsync(channelType, channelId, eventBody);
}

public Task<SyncResponseInternalDTO> SyncAsync(SyncRequestInternalDTO syncRequest)
=> Post<SyncRequestInternalDTO, SyncResponseInternalDTO>($"/sync", syncRequest);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using StreamChat.Core.Helpers;
using StreamChat.Core.InternalDTO.Events;
using StreamChat.Core.InternalDTO.Models;
using StreamChat.Core.LowLevelClient.Models;

namespace StreamChat.Core.LowLevelClient.Events
{
public partial class EventCustom : EventBase, ILoadableFrom<CustomEventInternalDTO, EventCustom>
{
public string ChannelId { get; set; }

public string ChannelType { get; set; }

public string Cid { get; set; }

public string Type { get; set; }

public User User { get; set; }

EventCustom ILoadableFrom<CustomEventInternalDTO, EventCustom>.LoadFromDto(CustomEventInternalDTO dto)
{
ChannelId = dto.ChannelId;
ChannelType = dto.ChannelType;
Cid = dto.Cid;
CreatedAt = dto.CreatedAt;
Type = dto.Type;
User = User.TryLoadFromDto<UserObjectInternalDTO, User>(dto.User);
AdditionalProperties = dto.AdditionalProperties;

return this;
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,14 @@ public interface IStreamRealtimeEventsProvider
/// <remarks>https://getstream.io/chat/docs/unity/event_object/?language=unity</remarks>
event Action<EventTypingStop> TypingStopped;

/// <summary>
/// Event raised when a custom event is received on a channel.
///
/// Use <see cref="EventCustom.Cid"/>, <see cref="EventCustom.Type"/>, and <see cref="EventCustom.User"/> to identify the channel, event type, and sender.
/// </summary>
/// <remarks>https://getstream.io/chat/docs/unity/event_object/?language=unity#custom-events</remarks>
event Action<EventCustom> CustomEventReceived;

/// <summary>
/// Notification Event raised when channel mutes are updated for local user.
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ public class StreamChatLowLevelClient : IStreamChatLowLevelClient
public event Action<EventTypingStart> TypingStarted;
public event Action<EventTypingStop> TypingStopped;

public event Action<EventCustom> CustomEventReceived;

public event Action<EventNotificationChannelMutesUpdated> NotificationChannelMutesUpdated;
public event Action<EventNotificationMutesUpdated> NotificationMutesUpdated;

Expand Down Expand Up @@ -150,6 +152,8 @@ public class StreamChatLowLevelClient : IStreamChatLowLevelClient
internal event Action<TypingStartEventInternalDTO> InternalTypingStarted;
internal event Action<TypingStopEventInternalDTO> InternalTypingStopped;

internal event Action<CustomEventInternalDTO> InternalCustomEventReceived;

internal event Action<NotificationChannelMutesUpdatedEventInternalDTO> InternalNotificationChannelMutesUpdated;
internal event Action<NotificationMutesUpdatedEventInternalDTO> InternalNotificationMutesUpdated;

Expand Down Expand Up @@ -957,6 +961,11 @@ private void HandleNewWebsocketMessage(string msg)

if (!_eventKeyToHandler.TryGetValue(type, out var handler))
{
if (TryHandleCustomChannelEvent(msg))
{
return;
}

if (_config.LogLevel.IsDebugEnabled())
{
_logs.Warning($"No message handler registered for `{type}`. Message not handled: " + msg);
Expand All @@ -968,6 +977,32 @@ private void HandleNewWebsocketMessage(string msg)
handler(msg);
}

private bool TryHandleCustomChannelEvent(string serializedContent)
{
if (!_serializer.TryPeekValue<string>(serializedContent, "cid", out var cid)
|| string.IsNullOrEmpty(cid))
{
return false;
}

try
{
var dto = _serializer.Deserialize<CustomEventInternalDTO>(serializedContent);
_lastEventReceivedAt = dto.CreatedAt;

var evt = new EventCustom();
((ILoadableFrom<CustomEventInternalDTO, EventCustom>)evt).LoadFromDto(dto);
CustomEventReceived?.Invoke(evt);
InternalCustomEventReceived?.Invoke(dto);
return true;
}
catch (Exception e)
{
_logs.Exception(e);
return false;
}
}

private void UpdateHealthCheck()
{
if (ConnectionState != ConnectionState.Connected)
Expand Down
25 changes: 25 additions & 0 deletions Assets/Plugins/StreamChat/Core/Models/IStreamCustomEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System;
using StreamChat.Core.StatefulModels;
using StreamChat.Core;

namespace StreamChat.Core.Models
{
/// <summary>
/// A custom event received on a channel.
/// </summary>
/// <remarks>https://getstream.io/chat/docs/unity/event_object/?language=unity#custom-events</remarks>
public interface IStreamCustomEvent
{
/// <summary>Custom event type, e.g. "friendship-request".</summary>
string Type { get; }

/// <summary>User who sent the event (resolved from cache).</summary>
IStreamUser User { get; }

/// <summary>Server timestamp of the event.</summary>
DateTimeOffset CreatedAt { get; }

/// <summary>Custom payload delivered with the event (top-level custom fields).</summary>
IStreamCustomData CustomData { get; }
}
}
11 changes: 11 additions & 0 deletions Assets/Plugins/StreamChat/Core/Models/IStreamCustomEvent.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 27 additions & 0 deletions Assets/Plugins/StreamChat/Core/Models/StreamCustomEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System;
using StreamChat.Core;
using StreamChat.Core.State;
using StreamChat.Core.StatefulModels;

namespace StreamChat.Core.Models
{
internal sealed class StreamCustomEvent : IStreamCustomEvent
{
public string Type { get; }

public IStreamUser User { get; }

public DateTimeOffset CreatedAt { get; }

public IStreamCustomData CustomData { get; }

internal StreamCustomEvent(string type, IStreamUser user, DateTimeOffset createdAt,
StreamCustomData customData)
{
Type = type;
User = user;
CreatedAt = createdAt;
CustomData = customData;
}
}
}
11 changes: 11 additions & 0 deletions Assets/Plugins/StreamChat/Core/Models/StreamCustomEvent.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using StreamChat.Core.LowLevelClient;
using StreamChat.Core.State.Caches;
using StreamChat.Libs.Logs;
using StreamChat.Libs.Serialization;

namespace StreamChat.Core.State
{
Expand Down Expand Up @@ -31,6 +32,7 @@ internal StreamStatefulModelBase(string uniqueId, ICacheRepository<TStatefulMode
Repository = repository ?? throw new ArgumentNullException(nameof(repository));

_customData = new StreamCustomData(_additionalProperties, context.Serializer);
Serializer = context.Serializer;

InternalUniqueId = uniqueId;
Repository.Track(Self);
Expand All @@ -43,6 +45,7 @@ internal StreamStatefulModelBase(string uniqueId, ICacheRepository<TStatefulMode

protected abstract TStatefulModel Self { get; }
protected StreamChatClient Client { get; }
protected ISerializer Serializer { get; }
protected StreamChatLowLevelClient LowLevelClient => Client.InternalLowLevelClient;
protected ILogs Logs { get; }
protected ICache Cache { get; }
Expand Down
16 changes: 16 additions & 0 deletions Assets/Plugins/StreamChat/Core/StatefulModels/IStreamChannel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,12 @@ public interface IStreamChannel : IStreamStatefulModel
/// </summary>
event StreamChannelUserChangeHandler UserStoppedTyping;

/// <summary>
/// Fired when a custom event (sent via <see cref="SendCustomEventAsync"/>) is received on this channel.
/// Only fires while the channel is watched (<see cref="IsWatched"/>).
/// </summary>
event StreamChannelCustomEventHandler CustomEventReceived;

/// <summary>
/// Event fired when a <see cref="TypingUsers"/> the list of typing users has changed.
/// If you want to exactly know when a users started or stopped typing subscribe to <see cref="UserStartedTyping"/> and <see cref="UserStoppedTyping"/>
Expand Down Expand Up @@ -586,6 +592,16 @@ Task TruncateAsync(DateTimeOffset? truncatedAt = default, string systemMessage =
/// </summary>
Task SendTypingStoppedEventAsync();

/// <summary>
/// Send a custom event to this channel. All members currently watching the channel
/// (including the local user) receive it via <see cref="CustomEventReceived"/>.
/// Requires the `send-custom-events` capability (see <see cref="OwnCapabilities"/>).
/// </summary>
/// <param name="eventType">Custom event type identifier, e.g. "friendship-request".</param>
/// <param name="customData">Optional custom key/value payload sent with the event.</param>
/// <remarks>https://getstream.io/chat/docs/unity/event_object/?language=unity#custom-events</remarks>
Task SendCustomEventAsync(string eventType, IDictionary<string, object> customData = null);

/// <summary>
/// Joins this channel as a a member (<see cref="IStreamChannelMember"/>). Only possible if local user has the `Join Own Channel` permission
/// </summary>
Expand Down
Loading
Loading