Skip to content

Commit c9702d1

Browse files
committed
Revert HereNow computation
1 parent 5978968 commit c9702d1

File tree

6 files changed

+64
-150
lines changed

6 files changed

+64
-150
lines changed

Documentation/PubNub_10_0_Migration_Guide.md

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,6 @@ pubNub.logger.debug("Custom debug message") // ❌ No longer available
7777
1. **New Parameters** - Two pagination parameters added:
7878
- `limit: Int` (default: 1000) - Maximum occupants per request
7979
- `offset: Int?` (default: 0) - Starting position for pagination
80-
2. **Response Format** - Changed from `[String: PubNubPresence]` to tuple `(presenceByChannel: [String: PubNubPresence], nextOffset: Int?)`
81-
3. **Pagination Control** - `nextOffset` provides the exact value for subsequent calls; `nil` means no more data
8280

8381
```swift
8482
// Before (9.0) - no pagination support:
@@ -90,7 +88,6 @@ pubnub.hereNow(
9088
) { result in
9189
switch result {
9290
case let .success(presenceByChannel):
93-
// Direct dictionary access - [String: PubNubPresence]
9491
for (channel, presence) in presenceByChannel {
9592
print("Channel: \(channel), Occupancy: \(presence.occupancy)")
9693
}
@@ -105,22 +102,15 @@ pubnub.hereNow(
105102
and: ["group1"],
106103
includeUUIDs: true,
107104
includeState: false,
108-
limit: 1000, // Maximum number of occupants to return per request
109-
offset: 0 // Starting position for pagination (0 = first page)
105+
limit: 1000, // Maximum number of occupants to return (per channel)
106+
offset: 0 // Starting position for pagination
110107
) { result in
111108
switch result {
112-
case let .success(response):
113-
// Tuple response with pagination info
114-
let presenceByChannel = response.presenceByChannel // [String: PubNubPresence]
115-
let nextOffset = response.nextOffset // Int? - offset for next page
116-
109+
case let .success(presenceByChannel):
117110
for (channel, presence) in presenceByChannel {
118111
print("Channel: \(channel), Occupancy: \(presence.occupancy)")
119-
}
120-
if let nextOffset = nextOffset {
121-
print("More results available at offset: \(nextOffset)")
122112
}
123-
case .failure(let error):
113+
case let .failure(error):
124114
print("Error: \(error)")
125115
}
126116
}

Snippets/Presence/presence.swift

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,12 @@ let pubnub = PubNub(
2929
// Get presence information for a channel
3030
pubnub.hereNow(on: ["my_channel"]) { result in
3131
switch result {
32-
case let .success(response):
33-
print("Total channels: \(response.presenceByChannel.count)")
34-
print("Total occupancy across all channels: \(response.presenceByChannel.totalOccupancy)")
32+
case let .success(presenceByChannel):
33+
print("Total channels: \(presenceByChannel.count)")
34+
print("Total occupancy across all channels: \(presenceByChannel.totalOccupancy)")
3535

36-
if let myChannelPresence = response.presenceByChannel["my_channel"] {
36+
if let myChannelPresence = presenceByChannel["my_channel"] {
37+
// Print the occupancy for the channel
3738
print("The occupancy for `my_channel` is \(myChannelPresence.occupancy)")
3839
// Iterating over each occupant in the channel and printing their UUID
3940
myChannelPresence.occupants.forEach { occupant in

Sources/PubNub/KMP/KMPPubNub+Presence.swift

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@ public extension KMPPubNub {
3939
case let .success(response):
4040
onSuccess(
4141
KMPHereNowResult(
42-
totalChannels: response.presenceByChannel.count,
43-
totalOccupancy: response.presenceByChannel.values.reduce(0, { accResult, channel in accResult + channel.occupancy }),
44-
channels: response.presenceByChannel.mapValues { value in
42+
totalChannels: response.count,
43+
totalOccupancy: response.values.reduce(0, { accResult, channel in accResult + channel.occupancy }),
44+
channels: response.mapValues { value in
4545
KMPHereNowChannelData(
4646
channelName: value.channel,
4747
occupancy: value.occupancy,
@@ -57,8 +57,7 @@ public extension KMPPubNub {
5757
)
5858
}
5959
)
60-
},
61-
nextOffset: response.nextOffset?.asNumber
60+
}
6261
)
6362
)
6463
case let .failure(error):

Sources/PubNub/KMP/Wrappers/KMPHereNowResult.swift

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,11 @@ public class KMPHereNowResult: NSObject {
2222
@objc public let totalChannels: Int
2323
@objc public let totalOccupancy: Int
2424
@objc public let channels: [String: KMPHereNowChannelData]
25-
@objc public let nextOffset: NSNumber?
2625

27-
init(totalChannels: Int, totalOccupancy: Int, channels: [String: KMPHereNowChannelData], nextOffset: NSNumber?) {
26+
init(totalChannels: Int, totalOccupancy: Int, channels: [String: KMPHereNowChannelData]) {
2827
self.totalChannels = totalChannels
2928
self.totalOccupancy = totalOccupancy
3029
self.channels = channels
31-
self.nextOffset = nextOffset
3230
}
3331
}
3432

Sources/PubNub/PubNub.swift

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -794,7 +794,7 @@ public extension PubNub {
794794
limit: Int = 1000,
795795
offset: Int? = 0,
796796
custom requestConfig: RequestConfiguration = RequestConfiguration(),
797-
completion: ((Result<(presenceByChannel: [String: PubNubPresence], nextOffset: Int?), Error>) -> Void)?
797+
completion: ((Result<[String: PubNubPresence], Error>) -> Void)?
798798
) {
799799
logger.debug(
800800
.customObject(
@@ -848,25 +848,7 @@ public extension PubNub {
848848
responseDecoder: decoder,
849849
custom: requestConfig
850850
) { result in
851-
completion?(result.map { response in
852-
// Convert the response to a dictionary of PubNubPresence objects
853-
let presenceByChannel: [String: PubNubPresence] = response.payload.asPubNubPresenceBase
854-
// Find the channel with the maximum number of fetched occupants and calculate next offset
855-
if let channelWithMaxOccupants = presenceByChannel.values.max(by: { $0.occupants.count < $1.occupants.count }) {
856-
let maxOccupantsCount = channelWithMaxOccupants.occupants.count
857-
let totalOccupancyForMaxChannel = channelWithMaxOccupants.occupancy
858-
let moreDataAvailable = currentOffset + maxOccupantsCount < totalOccupancyForMaxChannel
859-
860-
return (
861-
presenceByChannel: presenceByChannel,
862-
nextOffset: moreDataAvailable ? currentOffset + maxOccupantsCount : nil
863-
)
864-
}
865-
return (
866-
presenceByChannel: presenceByChannel,
867-
nextOffset: nil
868-
)
869-
})
851+
completion?(result.map { $0.payload.asPubNubPresenceBase })
870852
}
871853
}
872854

@@ -1427,6 +1409,7 @@ public extension PubNub {
14271409
}
14281410

14291411
/// Disables APNS2 push notifications on provided set of channels.
1412+
///
14301413
/// - Parameters:
14311414
/// - removals: The list of channels to disable registration
14321415
/// - device: The device to add/remove from the channels

Tests/PubNubTests/Integration/PresenceEndpointIntegrationTests.swift

Lines changed: 47 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@ class PresenceEndpointIntegrationTests: XCTestCase {
2222
let performHereNow = {
2323
client.hereNow(on: [testChannel], includeState: true) { result in
2424
switch result {
25-
case let .success(response):
26-
XCTAssertNotNil(response.presenceByChannel[testChannel])
27-
XCTAssertGreaterThan(response.presenceByChannel.totalChannels, 0)
28-
XCTAssertGreaterThan(response.presenceByChannel.totalOccupancy, 0)
25+
case let .success(presenceByChannel):
26+
XCTAssertNotNil(presenceByChannel[testChannel])
27+
XCTAssertGreaterThan(presenceByChannel.totalChannels, 0)
28+
XCTAssertGreaterThan(presenceByChannel.totalOccupancy, 0)
2929
case let .failure(error):
3030
XCTFail("Failed due to error: \(error)")
3131
}
@@ -56,9 +56,9 @@ class PresenceEndpointIntegrationTests: XCTestCase {
5656
let performHereNow = { [unowned client] in
5757
client.hereNow(on: [testChannel], includeState: true) { result in
5858
switch result {
59-
case let .success(response):
60-
XCTAssertNotNil(response.presenceByChannel[testChannel])
61-
XCTAssertEqual(response.presenceByChannel[testChannel]?.occupantsState[client.configuration.userId]?.codableValue, ["StateKey": "StateValue"])
59+
case let .success(presenceByChannel):
60+
XCTAssertNotNil(presenceByChannel[testChannel])
61+
XCTAssertEqual(presenceByChannel[testChannel]?.occupantsState[client.configuration.userId]?.codableValue, ["StateKey": "StateValue"])
6262
case let .failure(error):
6363
XCTFail("Failed due to error: \(error)")
6464
}
@@ -99,9 +99,9 @@ class PresenceEndpointIntegrationTests: XCTestCase {
9999

100100
client.hereNow(on: [testChannel], includeState: true) { result in
101101
switch result {
102-
case let .success(response):
103-
XCTAssertNotNil(response.presenceByChannel[testChannel])
104-
XCTAssertEqual(response.presenceByChannel.totalOccupancy, 0)
102+
case let .success(presenceByChannel):
103+
XCTAssertNotNil(presenceByChannel[testChannel])
104+
XCTAssertEqual(presenceByChannel.totalOccupancy, 0)
105105
case let .failure(error):
106106
XCTFail("Failed due to error: \(error)")
107107
}
@@ -120,8 +120,8 @@ class PresenceEndpointIntegrationTests: XCTestCase {
120120
let performHereNow = {
121121
client.hereNow(on: [testChannel, otherChannel]) { result in
122122
switch result {
123-
case let .success(response):
124-
XCTAssertNotNil(response.presenceByChannel[testChannel])
123+
case let .success(presenceByChannel):
124+
XCTAssertNotNil(presenceByChannel[testChannel])
125125
case let .failure(error):
126126
XCTFail("Failed due to error: \(error)")
127127
}
@@ -153,10 +153,10 @@ class PresenceEndpointIntegrationTests: XCTestCase {
153153
let performHereNow = {
154154
client.hereNow(on: [testChannel, otherChannel], includeState: true) { result in
155155
switch result {
156-
case let .success(response):
157-
XCTAssertNotNil(response.presenceByChannel[testChannel])
158-
XCTAssertEqual(response.presenceByChannel[testChannel]?.occupantsState[client.configuration.userId]?.codableValue, ["StateKey": "StateValue"])
159-
XCTAssertEqual(response.presenceByChannel[otherChannel]?.occupantsState[client.configuration.userId]?.codableValue, ["StateKey": "StateValue"])
156+
case let .success(presenceByChannel):
157+
XCTAssertNotNil(presenceByChannel[testChannel])
158+
XCTAssertEqual(presenceByChannel[testChannel]?.occupantsState[client.configuration.userId]?.codableValue, ["StateKey": "StateValue"])
159+
XCTAssertEqual(presenceByChannel[otherChannel]?.occupantsState[client.configuration.userId]?.codableValue, ["StateKey": "StateValue"])
160160
case let .failure(error):
161161
XCTFail("Failed due to error: \(error)")
162162
}
@@ -199,8 +199,8 @@ class PresenceEndpointIntegrationTests: XCTestCase {
199199

200200
client.hereNow(on: [testChannel, otherChannel], includeState: true) { result in
201201
switch result {
202-
case let .success(response):
203-
XCTAssertTrue(response.presenceByChannel.isEmpty)
202+
case let .success(presenceByChannel):
203+
XCTAssertTrue(presenceByChannel.isEmpty)
204204
case let .failure(error):
205205
XCTFail("Failed due to error: \(error)")
206206
}
@@ -210,89 +210,47 @@ class PresenceEndpointIntegrationTests: XCTestCase {
210210
wait(for: [hereNowExpect], timeout: 10.0)
211211
}
212212

213-
func testHereNow_MultiChannel_OffsetAndLimit() {
214-
let hereNowExpect = expectation(description: "Here Now Pagination Response")
215-
let lobbyChannel = "lobby"
216-
let generalChannel = "general"
217-
let vipChannel = "vip"
213+
func testHereNow_MultiChannel_Limit() {
214+
let hereNowExpect = expectation(description: "Here Now Limit Response")
215+
let testChannel = "testHereNow_Limit"
218216

219-
// Create 5 clients
217+
// Create 4 clients
220218
let clientA = PubNub(configuration: presenceConfiguration())
221219
let clientB = PubNub(configuration: presenceConfiguration())
222220
let clientC = PubNub(configuration: presenceConfiguration())
223221
let clientD = PubNub(configuration: presenceConfiguration())
224222

225-
// Expected users per channel
226-
let lobbyUsers = Set([clientA.configuration.userId, clientB.configuration.userId, clientC.configuration.userId, clientD.configuration.userId])
227-
let generalUsers = Set([clientA.configuration.userId, clientB.configuration.userId, clientC.configuration.userId])
228-
let vipUsers = Set([clientA.configuration.userId, clientB.configuration.userId])
223+
// Expected users to join the channel
224+
let expectedUsers = Set([
225+
clientA.configuration.userId,
226+
clientB.configuration.userId,
227+
clientC.configuration.userId,
228+
clientD.configuration.userId
229+
])
229230

230231
let performHereNow = {
231-
clientA.hereNow(on: [lobbyChannel, generalChannel, vipChannel], limit: 2) { [unowned clientA] result in
232+
clientA.hereNow(on: [testChannel], limit: 2) { result in
232233
switch result {
233-
case let .success(firstPage):
234-
// Verify total channels matches the number of channels and total occupancy matches the number of users
235-
XCTAssertEqual(firstPage.presenceByChannel.totalChannels, 3)
236-
XCTAssertEqual(firstPage.presenceByChannel.totalOccupancy, 9)
237-
238-
// Verify occupancy for each provided channel
239-
XCTAssertEqual(firstPage.presenceByChannel[lobbyChannel]?.occupancy, 4)
240-
XCTAssertEqual(firstPage.presenceByChannel[generalChannel]?.occupancy, 3)
241-
XCTAssertEqual(firstPage.presenceByChannel[vipChannel]?.occupancy, 2)
242-
243-
let firstPageLobby = firstPage.presenceByChannel[lobbyChannel]?.occupants ?? []
244-
let firstPageGeneral = firstPage.presenceByChannel[generalChannel]?.occupants ?? []
245-
let firstPageVip = firstPage.presenceByChannel[vipChannel]?.occupants ?? []
246-
247-
// Verify channel occupants don't exceed the limit
248-
XCTAssertEqual(firstPageLobby.count, 2)
249-
XCTAssertEqual(firstPageGeneral.count, 2)
250-
XCTAssertEqual(firstPageVip.count, 2)
251-
252-
// Fetch second page using offset
253-
clientA.hereNow(on: [lobbyChannel, generalChannel, vipChannel], offset: firstPage.nextOffset) { secondResult in
254-
switch secondResult {
255-
case let .success(secondPage):
256-
let secondPageLobby = secondPage.presenceByChannel[lobbyChannel]?.occupants ?? []
257-
let secondPageGeneral = secondPage.presenceByChannel[generalChannel]?.occupants ?? []
258-
let secondPageVip = secondPage.presenceByChannel[vipChannel]?.occupants ?? []
259-
260-
// Verify pagination - lobby channel should have 2 more users (4 total)
261-
let allLobbyUsers = Set(firstPageLobby + secondPageLobby)
262-
XCTAssertEqual(secondPageLobby.count, 2)
263-
XCTAssertEqual(allLobbyUsers, lobbyUsers)
264-
265-
// Verify pagination - general channel should have 1 more user (3 total)
266-
let allGeneralUsers = Set(firstPageGeneral + secondPageGeneral)
267-
XCTAssertEqual(secondPageGeneral.count, 1)
268-
XCTAssertEqual(allGeneralUsers, generalUsers)
269-
270-
// Verify pagination - vip channel should have 0 more users (2 total from first page)
271-
let allVipUsers = Set(firstPageVip + secondPageVip)
272-
XCTAssertEqual(secondPageVip.count, 0)
273-
XCTAssertEqual(allVipUsers, vipUsers)
274-
275-
case let .failure(error):
276-
XCTFail("Failed due to error: \(error)")
277-
}
278-
hereNowExpect.fulfill()
279-
}
234+
case let .success(response):
235+
XCTAssertEqual((response[testChannel]?.occupants ?? []).count, 2)
280236
case let .failure(error):
281237
XCTFail("Failed due to error: \(error)")
282238
}
239+
hereNowExpect.fulfill()
283240
}
284241
}
285242

286-
let listener = waitOnMultiChannelPresence(
243+
let listener = waitOnPresence(
287244
client: clientA,
288-
channelUsers: [lobbyChannel: lobbyUsers, generalChannel: generalUsers, vipChannel: vipUsers],
245+
channel: testChannel,
246+
userIds: expectedUsers,
289247
completion: performHereNow
290248
)
291249

292-
clientA.subscribe(to: [lobbyChannel, generalChannel, vipChannel], withPresence: true)
293-
clientB.subscribe(to: [lobbyChannel, generalChannel, vipChannel], withPresence: true)
294-
clientC.subscribe(to: [lobbyChannel, generalChannel], withPresence: true)
295-
clientD.subscribe(to: [lobbyChannel], withPresence: true)
250+
clientA.subscribe(to: [testChannel], withPresence: true)
251+
clientB.subscribe(to: [testChannel], withPresence: true)
252+
clientC.subscribe(to: [testChannel], withPresence: true)
253+
clientD.subscribe(to: [testChannel], withPresence: true)
296254

297255
defer { listener.cancel() }
298256
wait(for: [hereNowExpect], timeout: 30.0)
@@ -347,32 +305,25 @@ private extension PresenceEndpointIntegrationTests {
347305
}
348306

349307
private extension PresenceEndpointIntegrationTests {
350-
func waitOnMultiChannelPresence(
351-
client: PubNub,
352-
channelUsers: [String: Set<String>],
353-
completion: @escaping () -> Void
354-
) -> SubscriptionListener {
308+
func waitOnPresence(client: PubNub, channel: String, userIds: Set<String>? = nil, completion: @escaping () -> Void) -> SubscriptionListener {
355309
let listener = SubscriptionListener()
356-
var joinedUsersByChannel: [String: Set<String>] = channelUsers.mapValues { _ in Set<String>() }
310+
let expectedUsers = userIds ?? Set([client.configuration.userId])
311+
var joinedUsers: Set<String> = Set<String>()
357312
var hasCompleted = false
358313

359314
listener.didReceivePresence = { event in
360-
guard !hasCompleted, let expectedUsers = channelUsers[event.channel] else {
315+
guard !hasCompleted, event.channel == channel else {
361316
return
362317
}
363318

364319
for action in event.actions {
365320
if case let .join(uuids) = action {
366-
joinedUsersByChannel[event.channel]?.formUnion(uuids.filter { expectedUsers.contains($0) })
321+
joinedUsers.formUnion(uuids.filter { expectedUsers.contains($0) })
367322
}
368323
}
369324

370-
// Check if all expected users have joined all channels
371-
let allJoined = channelUsers.allSatisfy { channel, expectedUsers in
372-
joinedUsersByChannel[channel] == expectedUsers
373-
}
374-
375-
if allJoined {
325+
// Check if all expected users have joined the channel
326+
if joinedUsers == expectedUsers {
376327
hasCompleted = true
377328
completion()
378329
}
@@ -384,12 +335,4 @@ private extension PresenceEndpointIntegrationTests {
384335
// Return listener to be able to cancel it when the test is done
385336
return listener
386337
}
387-
388-
func waitOnPresence(client: PubNub, channel: String, userIds: Set<String>? = nil, completion: @escaping () -> Void) -> SubscriptionListener {
389-
waitOnMultiChannelPresence(
390-
client: client,
391-
channelUsers: [channel: userIds ?? Set([client.configuration.userId])],
392-
completion: completion
393-
)
394-
}
395338
}

0 commit comments

Comments
 (0)