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
21 changes: 14 additions & 7 deletions .pubnub.yml
Original file line number Diff line number Diff line change
@@ -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:
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -417,12 +417,21 @@ public static T DeserializeToInternalObject<T>(IJsonPluggableLibrary jsonPlug, L
}

hereNowResult.Channels = new Dictionary<string, PNHereNowChannelData>();
// This block handles the "compact" here_now response shape that the server returns

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is where different response shapes are being handled for hereNow

// 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<PNHereNowOccupantData> uuidDataList = new List<PNHereNowOccupantData>();
object[] uuidArray = jsonPlug.ConvertToObjectArray(herenowDicObj["uuids"]);
if (uuidArray != null && uuidArray.Length > 0)
{
List<PNHereNowOccupantData> uuidDataList = new List<PNHereNowOccupantData>();
for (int index = 0; index < uuidArray.Length; index++)
{
Dictionary<string, object> hereNowChannelItemUuidsDic =
Expand All @@ -446,25 +455,34 @@ public static T DeserializeToInternalObject<T>(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
{
// 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;
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++;
}
Expand Down
4 changes: 2 additions & 2 deletions src/Api/PubnubApi/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
5 changes: 3 additions & 2 deletions src/Api/PubnubApi/PubnubApi.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,16 @@

<PropertyGroup>
<PackageId>Pubnub</PackageId>
<PackageVersion>8.2.0</PackageVersion>
<PackageVersion>8.2.1</PackageVersion>
<Title>PubNub C# .NET - Web Data Push API</Title>
<Authors>Pandu Masabathula</Authors>
<Owners>PubNub</Owners>
<PackageLicenseFile>LICENSE.txt</PackageLicenseFile>
<PackageIconUrl>http://pubnub.s3.amazonaws.com/2011/powered-by-pubnub/pubnub-icon-600x600.png</PackageIconUrl>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<RepositoryUrl>https://github.com/pubnub/c-sharp/</RepositoryUrl>
<PackageReleaseNotes>Added support for the publish v2 endpoint, allowing feature-enabled keysets to publish messages of size up to 2MB.</PackageReleaseNotes>
<PackageReleaseNotes>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.</PackageReleaseNotes>
<PackageTags>Web Data Push Real-time Notifications ESB Message Broadcasting Distributed Computing</PackageTags>
<!--<Summary>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</Summary>-->
<Description>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</Description>
Expand Down
5 changes: 3 additions & 2 deletions src/Api/PubnubApiPCL/PubnubApiPCL.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,16 @@

<PropertyGroup>
<PackageId>PubnubPCL</PackageId>
<PackageVersion>8.2.0</PackageVersion>
<PackageVersion>8.2.1</PackageVersion>
<Title>PubNub C# .NET - Web Data Push API</Title>
<Authors>Pandu Masabathula</Authors>
<Owners>PubNub</Owners>
<PackageLicenseFile>LICENSE.txt</PackageLicenseFile>
<PackageIconUrl>http://pubnub.s3.amazonaws.com/2011/powered-by-pubnub/pubnub-icon-600x600.png</PackageIconUrl>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<RepositoryUrl>https://github.com/pubnub/c-sharp/</RepositoryUrl>
<PackageReleaseNotes>Added support for the publish v2 endpoint, allowing feature-enabled keysets to publish messages of size up to 2MB.</PackageReleaseNotes>
<PackageReleaseNotes>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.</PackageReleaseNotes>
<PackageTags>Web Data Push Real-time Notifications ESB Message Broadcasting Distributed Computing</PackageTags>
<!--<Summary>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</Summary>-->
<Description>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</Description>
Expand Down
5 changes: 3 additions & 2 deletions src/Api/PubnubApiUWP/PubnubApiUWP.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,16 @@

<PropertyGroup>
<PackageId>PubnubUWP</PackageId>
<PackageVersion>8.2.0</PackageVersion>
<PackageVersion>8.2.1</PackageVersion>
<Title>PubNub C# .NET - Web Data Push API</Title>
<Authors>Pandu Masabathula</Authors>
<Owners>PubNub</Owners>
<PackageLicenseFile>LICENSE.txt</PackageLicenseFile>
<PackageIconUrl>http://pubnub.s3.amazonaws.com/2011/powered-by-pubnub/pubnub-icon-600x600.png</PackageIconUrl>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<RepositoryUrl>https://github.com/pubnub/c-sharp/</RepositoryUrl>
<PackageReleaseNotes>Added support for the publish v2 endpoint, allowing feature-enabled keysets to publish messages of size up to 2MB.</PackageReleaseNotes>
<PackageReleaseNotes>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.</PackageReleaseNotes>
<PackageTags>Web Data Push Real-time Notifications ESB Message Broadcasting Distributed Computing</PackageTags>
<!--<Summary>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</Summary>-->
<Description>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</Description>
Expand Down
2 changes: 1 addition & 1 deletion src/Api/PubnubApiUnity/PubnubApiUnity.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

<PropertyGroup>
<PackageId>PubnubApiUnity</PackageId>
<PackageVersion>8.2.0</PackageVersion>
<PackageVersion>8.2.1</PackageVersion>
<Title>PubNub C# .NET - Web Data Push API</Title>
<Authors>Pandu Masabathula</Authors>
<Owners>PubNub</Owners>
Expand Down
113 changes: 113 additions & 0 deletions src/UnitTests/PubnubApi.Tests/WhenAClientIsPresented.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<object> listObject = new List<object>
{
pn.JsonPluggableLibrary.DeserializeToObject(serverResponse),
channelArg
};
return DeserializeToInternalObjectUtility
.DeserializeToInternalObject<PNHereNowResult>(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()
{
Expand Down
Loading