Skip to content

Commit f7b467f

Browse files
chore: release version 4.2.0 (#22)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent 241a88d commit f7b467f

File tree

182 files changed

+6272
-1440
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

182 files changed

+6272
-1440
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# Change Log
22

3+
## 4.2.0 (Feb 10, 2026)
4+
### Improvements
5+
- Improved JSON deserialization performance and reduced memory allocations
6+
- Reduced memory usage for API responses
7+
8+
### Bug Fixes
9+
- Fixed WebSocket memory leak on WebGL platform
10+
311
## 4.1.4 (Feb 10, 2026)
412
### Bug Fixes
513
- Fixed SuperGroupChannel event handling for user join and leave

Runtime/Plugins/WebGL/SendbirdWebSocket.jslib

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ const SendbirdWebSocketBridge = {
5555
// Delete the WebSocket client by ID
5656
SendbirdWebSocketBridge_DeleteWebSocketClient: function (clientId) {
5757
if (bridgeContext.clients[clientId] != null) {
58+
var client = bridgeContext.clients[clientId];
59+
if (client.webSocket != null && client.webSocket.readyState !== WebSocket.CLOSING && client.webSocket.readyState !== WebSocket.CLOSED) {
60+
client.webSocket.close();
61+
}
5862
delete bridgeContext.clients[clientId];
5963
} else {
6064
console.warn('DeleteWebSocketClient WebSocket client not found with ID: ' + clientId);
@@ -89,6 +93,7 @@ const SendbirdWebSocketBridge = {
8993
var stringPtr = _malloc(lengthBytes);
9094
stringToUTF8(event.data, stringPtr, lengthBytes);
9195
bridgeContext.invokeOnMessageCallback(client.clientId, stringPtr);
96+
_free(stringPtr);
9297
} else {
9398
console.warn("WebSocket received invalid message type:", (typeof event.data));
9499
}
@@ -124,13 +129,13 @@ const SendbirdWebSocketBridge = {
124129
SendbirdWebSocketBridge_Close: function (clientId) {
125130
const client = bridgeContext.clients[clientId];
126131
if (!client) {
127-
console.warn('SendbirdWebSocketBridge_Send client not found with ID: ' + clientId);
132+
console.warn('SendbirdWebSocketBridge_Close client not found with ID: ' + clientId);
128133
bridgeContext.invokeOnErrorCallback(clientId);
129134
return;
130135
}
131136

132137
if (client.webSocket != null && client.webSocket.readyState !== WebSocket.CLOSING && client.webSocket.readyState !== WebSocket.CLOSED) {
133-
client.webSocket.Close();
138+
client.webSocket.close();
134139
} else {
135140
console.warn('Close WebSocket client not found with ID: ' + clientId);
136141
bridgeContext.invokeOnErrorCallback(clientId);

Runtime/Scripts/Internal/ChatClient/Channel/BaseChannel/SbBaseChannel+MetaCounters.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,16 +58,19 @@ private void GetMetaCountersInternal(List<string> inKeys, SbMetaCountersHandler
5858

5959
void OnCompletionHandler(ApiCommandAbstract.Response inResponse, SbError inError, bool inIsCanceled)
6060
{
61+
if (inError != null)
62+
{
63+
inCompletionHandler?.Invoke(null, inError);
64+
return;
65+
}
66+
6167
if (inResponse is GetMetaCountersApiCommand.Response getMetaCountersResponse)
6268
{
6369
inCompletionHandler?.Invoke(getMetaCountersResponse.MetaCounters, null);
6470
}
6571
else
6672
{
67-
if (inError != null)
68-
inError = SbErrorCodeExtension.MALFORMED_DATA_ERROR;
69-
70-
inCompletionHandler?.Invoke(null, inError);
73+
inCompletionHandler?.Invoke(null, SbErrorCodeExtension.MALFORMED_DATA_ERROR);
7174
}
7275
}
7376

Runtime/Scripts/Internal/ChatClient/Channel/BaseChannelExtends/GroupChannel/SbGroupChannel+Internal.cs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,24 @@ private protected override void OnResetFromChannelDto(BaseChannelDto inBaseChann
8080
_joinedAt = groupChannelDto.joinedTs;
8181

8282
if (groupChannelDto.LastMessage != null)
83-
_lastMessage = groupChannelDto.LastMessage.CreateMessageInstance(chatMainContextRef);
83+
{
84+
if (_lastMessage == null ||
85+
_lastMessage.MessageId != groupChannelDto.LastMessage.MessageId ||
86+
_lastMessage.UpdatedAt < groupChannelDto.LastMessage.updatedAt)
87+
{
88+
_lastMessage = groupChannelDto.LastMessage.CreateMessageInstance(chatMainContextRef);
89+
}
90+
}
8491

8592
if (groupChannelDto.LatestPinnedMessage != null)
86-
_lastPinnedMessage = groupChannelDto.LatestPinnedMessage.CreateMessageInstance(chatMainContextRef);
93+
{
94+
if (_lastPinnedMessage == null ||
95+
_lastPinnedMessage.MessageId != groupChannelDto.LatestPinnedMessage.MessageId ||
96+
_lastPinnedMessage.UpdatedAt < groupChannelDto.LatestPinnedMessage.updatedAt)
97+
{
98+
_lastPinnedMessage = groupChannelDto.LatestPinnedMessage.CreateMessageInstance(chatMainContextRef);
99+
}
100+
}
87101

88102
SetMessageOffsetTimestamp(groupChannelDto.tsMessageOffset);
89103
_messageSurvivalSeconds = groupChannelDto.messageSurvivalSeconds;

Runtime/Scripts/Internal/ChatClient/Channel/ChannelManager/BaseChannelManagerExtends/GroupChannelManager/GroupChannelManager+WsEvent.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,9 @@ void OnGetChannelCompletionHandler(SbGroupChannel inGroupChannel, bool inIsFromC
9494
if (baseMessageDto.mentionedUserDtos != null)
9595
inGroupChannel.UpdateMembers(baseMessageDto.mentionedUserDtos);
9696

97+
JsonMemoryProfiler.TakeSnapshot("GroupChannel:BeforeCreateMessageInstance");
9798
SbBaseMessage message = baseMessageDto.CreateMessageInstance(chatMainContextRef);
99+
JsonMemoryProfiler.TakeSnapshot("GroupChannel:AfterCreateMessageInstance");
98100

99101
if (inGroupChannel.ShouldUpdateLsatMessage(message, baseMessageDto.forceUpdateLastMessage))
100102
{
@@ -113,6 +115,7 @@ void OnGetChannelCompletionHandler(SbGroupChannel inGroupChannel, bool inIsFromC
113115

114116
if (inMessageWsReceiveCommand.IsCreatedFromCurrentDevice() == false)
115117
{
118+
JsonMemoryProfiler.TakeSnapshot("GroupChannel:BeforeOnMessageReceived");
116119
channelHandlersById.ForEachByValue(inHandler => { inHandler.OnMessageReceived?.Invoke(inGroupChannel, message); });
117120
chatMainContextRef.CollectionManager.OnMessageReceived(inGroupChannel, message);
118121

Runtime/Scripts/Internal/ChatClient/Message/SbBaseMessage+Internal.cs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -146,24 +146,25 @@ private protected SbBaseMessage(BaseMessageDto inBaseMessageDto, SendbirdChatMai
146146
}
147147
}
148148

149-
if (inBaseMessageDto.reactionDtos != null && 0 < inBaseMessageDto.reactionDtos.Count)
149+
var reactionDtos = inBaseMessageDto.ReactionDtos;
150+
if (reactionDtos != null && 0 < reactionDtos.Count)
150151
{
151-
_reactions = new List<SbReaction>(inBaseMessageDto.reactionDtos.Count);
152-
foreach (ReactionDto reactionDto in inBaseMessageDto.reactionDtos)
152+
_reactions = new List<SbReaction>(reactionDtos.Count);
153+
foreach (ReactionDto reactionDto in reactionDtos)
153154
{
154155
SbReaction reaction = new SbReaction(reactionDto);
155156
_reactions.Add(reaction);
156157
}
157158
}
158159

159-
if (inBaseMessageDto.ogMetaDataDto != null)
160-
_ogMetaData = new SbOgMetaData(inBaseMessageDto.ogMetaDataDto);
160+
if (inBaseMessageDto.OgMetaDataDto != null)
161+
_ogMetaData = new SbOgMetaData(inBaseMessageDto.OgMetaDataDto);
161162

162163
if (inBaseMessageDto.appleCriticalAlertOptionsDto != null)
163164
_appleCriticalAlertOptions = new SbAppleCriticalAlertOptions(inBaseMessageDto.appleCriticalAlertOptionsDto);
164165

165-
if (inBaseMessageDto.threadInfoDto != null)
166-
_threadInfo = new SbThreadInfo(inBaseMessageDto.threadInfoDto, chatMainContextRef);
166+
if (inBaseMessageDto.ThreadInfoDto != null)
167+
_threadInfo = new SbThreadInfo(inBaseMessageDto.ThreadInfoDto, chatMainContextRef);
167168
}
168169

169170
private protected SbBaseMessage(SbBaseMessageCreateParams inBaseMessageCreateParams, SendbirdChatMainContext inChatMainContext,

Runtime/Scripts/Internal/ChatClient/SendbirdChatMain/SendbirdChatMain+WsEvent.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,9 @@ void ICommandRouterEventListener.OnReceiveWsEventCommand(WsReceiveCommandAbstrac
6969
ChatMainContext.SessionManager.OnReceiveWsEventCommand(inWsReceiveCommand);
7070
ChatMainContext.ConnectionManager.OnReceiveWsEventCommand(inWsReceiveCommand);
7171
ChatMainContext.OpenChannelManager.OnReceiveWsEventCommand(inWsReceiveCommand);
72+
JsonMemoryProfiler.TakeSnapshot("WsEvent:BeforeGroupChannelManager");
7273
ChatMainContext.GroupChannelManager.OnReceiveWsEventCommand(inWsReceiveCommand);
74+
JsonMemoryProfiler.TakeSnapshot("WsEvent:AfterGroupChannelManager");
7375
}
7476
}
7577
}

Runtime/Scripts/Internal/Command/ApiCommand/ApiCommandAbstract.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,15 @@ internal abstract class PutRequest : MultipartRequest
212212
internal class Response
213213
{
214214
internal virtual void OnResponseAfterDeserialize(string inJsonString) { }
215+
216+
internal virtual void OnResponseAfterDeserialize(byte[] inResponseBytes)
217+
{
218+
if (inResponseBytes == null || inResponseBytes.Length == 0)
219+
return;
220+
221+
string jsonString = Encoding.UTF8.GetString(inResponseBytes);
222+
OnResponseAfterDeserialize(jsonString);
223+
}
215224
}
216225
}
217226
}

Runtime/Scripts/Internal/Command/ApiCommand/ApiCommandExtends/Channel/BannedUserListQueryApiCommand.cs

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// Copyright (c) 2022 Sendbird, Inc.
33
//
44

5-
using System;
65
using System.Collections.Generic;
76
using System.Net;
87
using Newtonsoft.Json;
@@ -25,11 +24,38 @@ internal Request(string inToken, int inLimit, SbChannelType inChannelType, strin
2524
}
2625
}
2726

28-
[Serializable]
2927
internal sealed class Response : ApiCommandAbstract.Response
3028
{
31-
[JsonProperty("next")] internal readonly string token;
32-
[JsonProperty("banned_list")] internal readonly List<RestrictedUserDto> restrictedUserDtos;
29+
internal string token;
30+
internal List<RestrictedUserDto> restrictedUserDtos;
31+
32+
internal override void OnResponseAfterDeserialize(string inJsonString)
33+
{
34+
if (string.IsNullOrEmpty(inJsonString))
35+
return;
36+
37+
using (JsonTextReader reader = JsonStreamingPool.CreateReader(inJsonString))
38+
{
39+
reader.Read();
40+
if (reader.TokenType != JsonToken.StartObject)
41+
return;
42+
43+
while (reader.Read())
44+
{
45+
if (reader.TokenType == JsonToken.EndObject)
46+
break;
47+
48+
string propName = reader.Value as string;
49+
reader.Read();
50+
switch (propName)
51+
{
52+
case "next": token = JsonStreamingHelper.ReadString(reader); break;
53+
case "banned_list": restrictedUserDtos = RestrictedUserDto.ReadListFromJson(reader); break;
54+
default: JsonStreamingHelper.SkipValue(reader); break;
55+
}
56+
}
57+
}
58+
}
3359
}
3460
}
3561
}

Runtime/Scripts/Internal/Command/ApiCommand/ApiCommandExtends/Channel/GroupChannel/CreateGroupChannelApiCommand.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
using System;
66
using System.Collections.Generic;
77
using Newtonsoft.Json;
8-
using Newtonsoft.Json.Linq;
98

109
namespace Sendbird.Chat
1110
{
@@ -116,7 +115,6 @@ internal Request(SbGroupChannelCreateParams inParams, ResultHandler inResultHand
116115
}
117116
}
118117

119-
[Serializable]
120118
internal sealed class Response : ApiCommandAbstract.Response
121119
{
122120
internal GroupChannelDto GroupChannelDto { get; private set; }

0 commit comments

Comments
 (0)