From fc29dfed72bfbb13f8f92e9d699478bdb5cd0cef Mon Sep 17 00:00:00 2001 From: Mohit Tejani Date: Fri, 29 May 2026 13:38:35 +0530 Subject: [PATCH 1/4] fix: response parsing for heneNow when the single channel occipancy requested without uuid details to assign channel occupancy to totalOccupancy returned by server, add empty channel data structure when there are no occupancy data in the response --- .../DeserializeToInternalObjectUtility.cs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/Api/PubnubApi/JsonDataParse/DeserializeToInternalObjectUtility.cs b/src/Api/PubnubApi/JsonDataParse/DeserializeToInternalObjectUtility.cs index 1ddb4bf83..623d309ca 100644 --- a/src/Api/PubnubApi/JsonDataParse/DeserializeToInternalObjectUtility.cs +++ b/src/Api/PubnubApi/JsonDataParse/DeserializeToInternalObjectUtility.cs @@ -419,10 +419,10 @@ public static T DeserializeToInternalObject(IJsonPluggableLibrary jsonPlug, L hereNowResult.Channels = new Dictionary(); if (herenowDicObj.ContainsKey("uuids")) { + List uuidDataList = new List(); object[] uuidArray = jsonPlug.ConvertToObjectArray(herenowDicObj["uuids"]); if (uuidArray != null && uuidArray.Length > 0) { - List uuidDataList = new List(); for (int index = 0; index < uuidArray.Length; index++) { Dictionary hereNowChannelItemUuidsDic = @@ -446,15 +446,15 @@ public static T DeserializeToInternalObject(IJsonPluggableLibrary jsonPlug, L uuidDataList.Add(uuidData); } } + } - PNHereNowChannelData channelData = new PNHereNowChannelData(); - channelData.ChannelName = hereNowChannelName; - channelData.Occupants = uuidDataList; - channelData.Occupancy = hereNowResult.TotalOccupancy; + PNHereNowChannelData channelData = new PNHereNowChannelData(); + channelData.ChannelName = hereNowChannelName; + channelData.Occupants = uuidDataList; + channelData.Occupancy = hereNowResult.TotalOccupancy; - hereNowResult.Channels.Add(hereNowChannelName, channelData); - hereNowResult.TotalChannels = hereNowResult.Channels.Count; - } + hereNowResult.Channels.Add(hereNowChannelName, channelData); + hereNowResult.TotalChannels = hereNowResult.Channels.Count; } else { @@ -464,7 +464,8 @@ public static T DeserializeToInternalObject(IJsonPluggableLibrary jsonPlug, L foreach (string channel in arrChannel) { PNHereNowChannelData channelData = new PNHereNowChannelData(); - channelData.Occupancy = 1; + channelData.ChannelName = channel; + channelData.Occupancy = hereNowResult.TotalOccupancy; hereNowResult.Channels.Add(channel, channelData); totalChannels++; } From 17fc505f2375bbb097c4d7721348420d22723389 Mon Sep 17 00:00:00 2001 From: Mohit Tejani Date: Fri, 29 May 2026 13:39:05 +0530 Subject: [PATCH 2/4] tests added for empty response and single channel occupancy without uuids cases. --- .../PubnubApi.Tests/WhenAClientIsPresented.cs | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/src/UnitTests/PubnubApi.Tests/WhenAClientIsPresented.cs b/src/UnitTests/PubnubApi.Tests/WhenAClientIsPresented.cs index 9a03af3c7..d35cc8d9d 100644 --- a/src/UnitTests/PubnubApi.Tests/WhenAClientIsPresented.cs +++ b/src/UnitTests/PubnubApi.Tests/WhenAClientIsPresented.cs @@ -626,6 +626,119 @@ public static async Task IfWithAsyncHereNowIsCalledThenItShouldReturnInfo() } + // Parses a raw here_now server response through the same deserializer the SDK uses, + // bypassing the network so occupancy parsing can be asserted deterministically. + private static PNHereNowResult ParseHereNowResponse(string serverResponse, string channelArg) + { + PNConfiguration config = new PNConfiguration(new UserId("mytestuuid")) + { + PublishKey = PubnubCommon.PublishKey, + SubscribeKey = PubnubCommon.SubscribeKey, + Secure = false + }; + Pubnub pn = createPubNubInstance(config, authToken); + try + { + List listObject = new List + { + pn.JsonPluggableLibrary.DeserializeToObject(serverResponse), + channelArg + }; + return DeserializeToInternalObjectUtility + .DeserializeToInternalObject(pn.JsonPluggableLibrary, listObject); + } + finally + { + pn.Destroy(); + pn.PubnubUnitTest = null; + } + } + + [Test] + public static void IfHereNowWithoutUUIDsIsCalledThenChannelOccupancyShouldMatchTotalOccupancy() + { + string channel = "mocha"; + // Single channel + disable_uuids returns the compact format: occupancy present, no payload wrapper, no uuids array. + string serverResponse = "{\"status\": 200, \"message\": \"OK\", \"service\": \"Presence\", \"occupancy\": 3}"; + + PNHereNowResult result = ParseHereNowResponse(serverResponse, channel); + + Assert.IsNotNull(result, "here_now result not parsed"); + Assert.AreEqual(3, result.TotalOccupancy, "TotalOccupancy mismatch"); + Assert.IsTrue(result.Channels.TryGetValue(channel, out var channelData), "channel data missing"); + Assert.AreEqual(channel, channelData.ChannelName, "ChannelName not populated"); + Assert.AreEqual(3, channelData.Occupancy, "channel Occupancy should match total occupancy, not be hardcoded to 1"); + } + + [Test] + public static void IfHereNowWithoutUUIDsIsCalledOnEmptyChannelThenOccupancyShouldBeZero() + { + string channel = "mocha"; + // Empty single channel + disable_uuids: occupancy 0, no uuids array. + string serverResponse = "{\"status\": 200, \"message\": \"OK\", \"service\": \"Presence\", \"occupancy\": 0}"; + + PNHereNowResult result = ParseHereNowResponse(serverResponse, channel); + + Assert.IsNotNull(result, "here_now result not parsed"); + Assert.AreEqual(0, result.TotalOccupancy, "TotalOccupancy mismatch"); + Assert.IsTrue(result.Channels.TryGetValue(channel, out var channelData), "channel data missing"); + Assert.AreEqual(channel, channelData.ChannelName, "ChannelName not populated"); + Assert.AreEqual(0, channelData.Occupancy, "empty channel occupancy should be 0, not hardcoded to 1"); + } + + [Test] + public static void IfHereNowWithUUIDsIsCalledThenChannelOccupancyShouldMatchTotalOccupancy() + { + string channel = "mocha"; + // Single channel + include uuids: uuids array present alongside occupancy. + string serverResponse = "{\"status\": 200, \"message\": \"OK\", \"service\": \"Presence\", \"uuids\": [\"u1\", \"u2\", \"u3\"], \"occupancy\": 3}"; + + PNHereNowResult result = ParseHereNowResponse(serverResponse, channel); + + Assert.IsNotNull(result, "here_now result not parsed"); + Assert.AreEqual(3, result.TotalOccupancy, "TotalOccupancy mismatch"); + Assert.IsTrue(result.Channels.TryGetValue(channel, out var channelData), "channel data missing"); + Assert.AreEqual(channel, channelData.ChannelName, "ChannelName not populated"); + Assert.AreEqual(3, channelData.Occupancy, "channel Occupancy should match total occupancy"); + Assert.AreEqual(3, channelData.Occupants.Count, "occupant count mismatch"); + } + + [Test] + public static void IfHereNowWithUUIDsIsCalledOnEmptyChannelThenChannelEntryShouldStillExist() + { + string channel = "mocha"; + // Empty single channel with uuids requested: uuids array present but empty, occupancy 0. + string serverResponse = "{\"status\": 200, \"message\": \"OK\", \"service\": \"Presence\", \"uuids\": [], \"occupancy\": 0}"; + + PNHereNowResult result = ParseHereNowResponse(serverResponse, channel); + + Assert.IsNotNull(result, "here_now result not parsed"); + Assert.AreEqual(0, result.TotalOccupancy, "TotalOccupancy mismatch"); + Assert.IsTrue(result.Channels.TryGetValue(channel, out var channelData), "channel entry should exist even when empty"); + Assert.AreEqual(channel, channelData.ChannelName, "ChannelName not populated"); + Assert.AreEqual(0, channelData.Occupancy, "empty channel occupancy should be 0"); + Assert.IsNotNull(channelData.Occupants, "Occupants should be an empty list, not null"); + Assert.AreEqual(0, channelData.Occupants.Count, "empty channel should have no occupants"); + } + + [Test] + public static void IfHereNowWithoutUUIDsIsCalledOnMultipleChannelsThenEachOccupancyShouldBeCorrect() + { + // Multiple channels always return the payload format with per-channel occupancy, even with disable_uuids. + string serverResponse = "{\"status\": 200, \"message\": \"OK\", \"payload\": {\"channels\": {\"mocha\": {\"occupancy\": 3}, \"chabc\": {\"occupancy\": 5}}, \"total_channels\": 2, \"total_occupancy\": 8}, \"service\": \"Presence\"}"; + + PNHereNowResult result = ParseHereNowResponse(serverResponse, "mocha,chabc"); + + Assert.IsNotNull(result, "here_now result not parsed"); + Assert.AreEqual(8, result.TotalOccupancy, "TotalOccupancy mismatch"); + Assert.AreEqual(2, result.TotalChannels, "TotalChannels mismatch"); + Assert.IsTrue(result.Channels.TryGetValue("mocha", out var mochaData), "mocha channel data missing"); + Assert.AreEqual(3, mochaData.Occupancy, "mocha occupancy mismatch"); + Assert.IsTrue(result.Channels.TryGetValue("chabc", out var chabcData), "chabc channel data missing"); + Assert.AreEqual(5, chabcData.Occupancy, "chabc occupancy mismatch"); + } + + [Test] public static void IfHereNowIsCalledThenItShouldReturnInfoCipher() { From 08056cdc362261b9352a050a4aaef8a54a10a52a Mon Sep 17 00:00:00 2001 From: Mohit Tejani Date: Fri, 29 May 2026 14:25:10 +0530 Subject: [PATCH 3/4] code comment added in json response parser to understand the flow of response parsing in various cases of HereNow responses --- .../DeserializeToInternalObjectUtility.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/Api/PubnubApi/JsonDataParse/DeserializeToInternalObjectUtility.cs b/src/Api/PubnubApi/JsonDataParse/DeserializeToInternalObjectUtility.cs index 623d309ca..89325c287 100644 --- a/src/Api/PubnubApi/JsonDataParse/DeserializeToInternalObjectUtility.cs +++ b/src/Api/PubnubApi/JsonDataParse/DeserializeToInternalObjectUtility.cs @@ -417,8 +417,17 @@ public static T DeserializeToInternalObject(IJsonPluggableLibrary jsonPlug, L } hereNowResult.Channels = new Dictionary(); + // This block handles the "compact" here_now response shape that the server returns + // ONLY for a single-channel request (no "payload" wrapper, just a flat "occupancy" plus + // an optional "uuids" list). The channel name itself is not in the body, so it is taken + // from listObject[1] (hereNowChannelName). if (herenowDicObj.ContainsKey("uuids")) { + // Case: single channel requested WITH uuids (disable_uuids=0). + // The channel entry is built and added unconditionally below — even when "uuids" is an + // empty array (i.e. an empty channel with occupancy 0). Previously the channel was only + // added when the array was non-empty, which silently dropped empty channels from the + // result; callers doing Channels.TryGetValue(channel, ...) would then get a false miss. List uuidDataList = new List(); object[] uuidArray = jsonPlug.ConvertToObjectArray(herenowDicObj["uuids"]); if (uuidArray != null && uuidArray.Length > 0) @@ -458,6 +467,14 @@ public static T DeserializeToInternalObject(IJsonPluggableLibrary jsonPlug, L } else { + // Case: single channel requested WITHOUT uuids (disable_uuids=1). + // The response carries only the total "occupancy" and no per-uuid list, so each + // channel's Occupancy is set from TotalOccupancy (the response's "occupancy" value). + // This compact shape is only ever returned for a SINGLE channel, so arrChannel has + // exactly one element and assigning TotalOccupancy to it is correct. (Multi-channel + // requests instead return the "payload" shape handled in the branch above, where each + // channel has its own occupancy.) Previously Occupancy here was hardcoded to 1, which + // reported the wrong count whenever the real occupancy was not 1 (e.g. 3 users -> 1). string channels = listObject[1].ToString(); string[] arrChannel = channels.Split(','); int totalChannels = 0; From 845d402cf1496d54d2a6a1b828755a9eeabc6bbf Mon Sep 17 00:00:00 2001 From: PubNub Release Bot <120067856+pubnub-release-bot@users.noreply.github.com> Date: Fri, 29 May 2026 21:22:44 +0000 Subject: [PATCH 4/4] PubNub SDK v8.2.1 release. --- .pubnub.yml | 21 +++++++++++++------- CHANGELOG | 5 +++++ src/Api/PubnubApi/Properties/AssemblyInfo.cs | 4 ++-- src/Api/PubnubApi/PubnubApi.csproj | 5 +++-- src/Api/PubnubApiPCL/PubnubApiPCL.csproj | 5 +++-- src/Api/PubnubApiUWP/PubnubApiUWP.csproj | 5 +++-- src/Api/PubnubApiUnity/PubnubApiUnity.csproj | 2 +- 7 files changed, 31 insertions(+), 16 deletions(-) diff --git a/.pubnub.yml b/.pubnub.yml index c9472b13c..57435ca1f 100644 --- a/.pubnub.yml +++ b/.pubnub.yml @@ -1,8 +1,15 @@ name: c-sharp -version: "8.2.0" +version: "8.2.1" schema: 1 scm: github.com/pubnub/c-sharp changelog: + - date: 2026-05-29 + version: v8.2.1 + changes: + - type: bug + text: "Fixes issue of hereNow returning incorrect Occupancy when Occupancy count value is omitted from the response of single channel hereNow call." + - type: bug + text: "Fix for `HereNow` omitting the channel entry for an empty channel when UUIDs are included." - date: 2026-05-18 version: v8.2.0 changes: @@ -982,14 +989,14 @@ features: - QUERY-PARAM supported-platforms: - - version: Pubnub 'C#' 8.2.0 + version: Pubnub 'C#' 8.2.1 platforms: - Windows 10 and up - Windows Server 2008 and up frameworks: - .Net Framework 4.5+ - - version: PubnubPCL 'C#' 8.2.0 + version: PubnubPCL 'C#' 8.2.1 platforms: - Xamarin.Android - Xamarin.iOS @@ -1001,7 +1008,7 @@ supported-platforms: frameworks: - .Net 4.5+ - - version: PubnubUWP 'C#' 8.2.0 + version: PubnubUWP 'C#' 8.2.1 platforms: - Windows Phone 10 - Universal Windows Apps @@ -1025,7 +1032,7 @@ sdks: distribution-type: source distribution-repository: GitHub package-name: Pubnub - location: https://github.com/pubnub/c-sharp/releases/tag/v8.2.0 + location: https://github.com/pubnub/c-sharp/releases/tag/v8.2.1 requires: - name: ".Net" @@ -1266,7 +1273,7 @@ sdks: distribution-type: source distribution-repository: GitHub package-name: PubNubPCL - location: https://github.com/pubnub/c-sharp/releases/tag/v8.2.0 + location: https://github.com/pubnub/c-sharp/releases/tag/v8.2.1 requires: - name: ".Net" @@ -1617,7 +1624,7 @@ sdks: distribution-type: source distribution-repository: GitHub package-name: PubnubUWP - location: https://github.com/pubnub/c-sharp/releases/tag/v8.2.0 + location: https://github.com/pubnub/c-sharp/releases/tag/v8.2.1 requires: - name: "Universal Windows Platform Development" diff --git a/CHANGELOG b/CHANGELOG index 3169c3d9b..de51427fd 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,8 @@ +v8.2.1 - May 29 2026 +----------------------------- +- Fixed: fixes issue of hereNow returning incorrect Occupancy when Occupancy count value is omitted from the response of single channel hereNow call. +- Fixed: fix for `HereNow` omitting the channel entry for an empty channel when UUIDs are included. + v8.2.0 - May 18 2026 ----------------------------- - Added: added support for the publish v2 endpoint, allowing feature-enabled keysets to publish messages of size up to 2MB. diff --git a/src/Api/PubnubApi/Properties/AssemblyInfo.cs b/src/Api/PubnubApi/Properties/AssemblyInfo.cs index 1badf7da7..d4dde423d 100644 --- a/src/Api/PubnubApi/Properties/AssemblyInfo.cs +++ b/src/Api/PubnubApi/Properties/AssemblyInfo.cs @@ -11,8 +11,8 @@ [assembly: AssemblyProduct("Pubnub C# SDK")] [assembly: AssemblyCopyright("Copyright © 2021")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("8.2.0")] -[assembly: AssemblyFileVersion("8.2.0")] +[assembly: AssemblyVersion("8.2.1")] +[assembly: AssemblyFileVersion("8.2.1")] // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. diff --git a/src/Api/PubnubApi/PubnubApi.csproj b/src/Api/PubnubApi/PubnubApi.csproj index 72c248d78..a38e0f745 100644 --- a/src/Api/PubnubApi/PubnubApi.csproj +++ b/src/Api/PubnubApi/PubnubApi.csproj @@ -14,7 +14,7 @@ Pubnub - 8.2.0 + 8.2.1 PubNub C# .NET - Web Data Push API Pandu Masabathula PubNub @@ -22,7 +22,8 @@ http://pubnub.s3.amazonaws.com/2011/powered-by-pubnub/pubnub-icon-600x600.png true https://github.com/pubnub/c-sharp/ - Added support for the publish v2 endpoint, allowing feature-enabled keysets to publish messages of size up to 2MB. + Fixes issue of hereNow returning incorrect Occupancy when Occupancy count value is omitted from the response of single channel hereNow call. +Fix for `HereNow` omitting the channel entry for an empty channel when UUIDs are included. Web Data Push Real-time Notifications ESB Message Broadcasting Distributed Computing PubNub is a Massively Scalable Web Push Service for Web and Mobile Games. This is a cloud-based service for broadcasting messages to thousands of web and mobile clients simultaneously diff --git a/src/Api/PubnubApiPCL/PubnubApiPCL.csproj b/src/Api/PubnubApiPCL/PubnubApiPCL.csproj index c35aee9c3..a94cb101f 100644 --- a/src/Api/PubnubApiPCL/PubnubApiPCL.csproj +++ b/src/Api/PubnubApiPCL/PubnubApiPCL.csproj @@ -14,7 +14,7 @@ PubnubPCL - 8.2.0 + 8.2.1 PubNub C# .NET - Web Data Push API Pandu Masabathula PubNub @@ -22,7 +22,8 @@ http://pubnub.s3.amazonaws.com/2011/powered-by-pubnub/pubnub-icon-600x600.png true https://github.com/pubnub/c-sharp/ - Added support for the publish v2 endpoint, allowing feature-enabled keysets to publish messages of size up to 2MB. + Fixes issue of hereNow returning incorrect Occupancy when Occupancy count value is omitted from the response of single channel hereNow call. +Fix for `HereNow` omitting the channel entry for an empty channel when UUIDs are included. Web Data Push Real-time Notifications ESB Message Broadcasting Distributed Computing PubNub is a Massively Scalable Web Push Service for Web and Mobile Games. This is a cloud-based service for broadcasting messages to thousands of web and mobile clients simultaneously diff --git a/src/Api/PubnubApiUWP/PubnubApiUWP.csproj b/src/Api/PubnubApiUWP/PubnubApiUWP.csproj index 92f2f2db0..35483149c 100644 --- a/src/Api/PubnubApiUWP/PubnubApiUWP.csproj +++ b/src/Api/PubnubApiUWP/PubnubApiUWP.csproj @@ -16,7 +16,7 @@ PubnubUWP - 8.2.0 + 8.2.1 PubNub C# .NET - Web Data Push API Pandu Masabathula PubNub @@ -24,7 +24,8 @@ http://pubnub.s3.amazonaws.com/2011/powered-by-pubnub/pubnub-icon-600x600.png true https://github.com/pubnub/c-sharp/ - Added support for the publish v2 endpoint, allowing feature-enabled keysets to publish messages of size up to 2MB. + Fixes issue of hereNow returning incorrect Occupancy when Occupancy count value is omitted from the response of single channel hereNow call. +Fix for `HereNow` omitting the channel entry for an empty channel when UUIDs are included. Web Data Push Real-time Notifications ESB Message Broadcasting Distributed Computing PubNub is a Massively Scalable Web Push Service for Web and Mobile Games. This is a cloud-based service for broadcasting messages to thousands of web and mobile clients simultaneously diff --git a/src/Api/PubnubApiUnity/PubnubApiUnity.csproj b/src/Api/PubnubApiUnity/PubnubApiUnity.csproj index 00f149e04..dc1c2bec3 100644 --- a/src/Api/PubnubApiUnity/PubnubApiUnity.csproj +++ b/src/Api/PubnubApiUnity/PubnubApiUnity.csproj @@ -15,7 +15,7 @@ PubnubApiUnity - 8.2.0 + 8.2.1 PubNub C# .NET - Web Data Push API Pandu Masabathula PubNub