Skip to content

Commit d89e340

Browse files
authored
Add missing heartbeat call when changing channels or channel groups (#211)
1 parent 0c7d895 commit d89e340

File tree

8 files changed

+85
-29
lines changed

8 files changed

+85
-29
lines changed

.pubnub.yml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
---
22
name: swift
33
scm: github.com/pubnub/swift
4-
version: "9.2.0"
4+
version: "9.2.1"
55
schema: 1
66
changelog:
7+
- date: 2025-06-20
8+
version: 9.2.1
9+
changes:
10+
- type: bug
11+
text: "Add missing heartbeat call when changing channels or channel groups."
712
- date: 2025-05-15
813
version: 9.2.0
914
changes:
@@ -688,7 +693,7 @@ sdks:
688693
- distribution-type: source
689694
distribution-repository: GitHub release
690695
package-name: PubNub
691-
location: https://github.com/pubnub/swift/archive/refs/tags/9.2.0.zip
696+
location: https://github.com/pubnub/swift/archive/refs/tags/9.2.1.zip
692697
supported-platforms:
693698
supported-operating-systems:
694699
macOS:

PubNub.xcodeproj/project.pbxproj

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4031,7 +4031,7 @@
40314031
"@loader_path/Frameworks",
40324032
);
40334033
MACOSX_DEPLOYMENT_TARGET = 10.15;
4034-
MARKETING_VERSION = 9.2.0;
4034+
MARKETING_VERSION = 9.2.1;
40354035
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++17";
40364036
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
40374037
MTL_FAST_MATH = YES;
@@ -4082,7 +4082,7 @@
40824082
"@loader_path/Frameworks",
40834083
);
40844084
MACOSX_DEPLOYMENT_TARGET = 10.15;
4085-
MARKETING_VERSION = 9.2.0;
4085+
MARKETING_VERSION = 9.2.1;
40864086
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++17";
40874087
MTL_ENABLE_DEBUG_INFO = NO;
40884088
MTL_FAST_MATH = YES;
@@ -4190,7 +4190,7 @@
41904190
"@loader_path/Frameworks",
41914191
);
41924192
MACOSX_DEPLOYMENT_TARGET = 10.15;
4193-
MARKETING_VERSION = 9.2.0;
4193+
MARKETING_VERSION = 9.2.1;
41944194
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++17";
41954195
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
41964196
MTL_FAST_MATH = YES;
@@ -4243,7 +4243,7 @@
42434243
"@loader_path/Frameworks",
42444244
);
42454245
MACOSX_DEPLOYMENT_TARGET = 10.15;
4246-
MARKETING_VERSION = 9.2.0;
4246+
MARKETING_VERSION = 9.2.1;
42474247
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++17";
42484248
MTL_ENABLE_DEBUG_INFO = NO;
42494249
MTL_FAST_MATH = YES;
@@ -4364,7 +4364,7 @@
43644364
"@loader_path/Frameworks",
43654365
);
43664366
MACOSX_DEPLOYMENT_TARGET = 10.15;
4367-
MARKETING_VERSION = 9.2.0;
4367+
MARKETING_VERSION = 9.2.1;
43684368
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++17";
43694369
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
43704370
MTL_FAST_MATH = YES;
@@ -4416,7 +4416,7 @@
44164416
"@loader_path/Frameworks",
44174417
);
44184418
MACOSX_DEPLOYMENT_TARGET = 10.15;
4419-
MARKETING_VERSION = 9.2.0;
4419+
MARKETING_VERSION = 9.2.1;
44204420
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++17";
44214421
MTL_ENABLE_DEBUG_INFO = NO;
44224422
MTL_FAST_MATH = YES;
@@ -4896,7 +4896,7 @@
48964896
"$(TOOLCHAIN_DIR)/usr/lib/swift/macosx",
48974897
);
48984898
MACOSX_DEPLOYMENT_TARGET = 10.15;
4899-
MARKETING_VERSION = 9.2.0;
4899+
MARKETING_VERSION = 9.2.1;
49004900
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++14";
49014901
OTHER_CFLAGS = "$(inherited)";
49024902
OTHER_LDFLAGS = "$(inherited)";
@@ -4939,7 +4939,7 @@
49394939
"$(TOOLCHAIN_DIR)/usr/lib/swift/macosx",
49404940
);
49414941
MACOSX_DEPLOYMENT_TARGET = 10.15;
4942-
MARKETING_VERSION = 9.2.0;
4942+
MARKETING_VERSION = 9.2.1;
49434943
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++14";
49444944
OTHER_CFLAGS = "$(inherited)";
49454945
OTHER_LDFLAGS = "$(inherited)";

PubNubSwift.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Pod::Spec.new do |s|
22
s.name = 'PubNubSwift'
3-
s.version = '9.2.0'
3+
s.version = '9.2.1'
44
s.homepage = 'https://github.com/pubnub/swift'
55
s.documentation_url = 'https://www.pubnub.com/docs/swift-native/pubnub-swift-sdk'
66
s.authors = { 'PubNub, Inc.' => '[email protected]' }

Sources/PubNub/EventEngine/Presence/PresenceTransition.swift

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class PresenceTransition: TransitionProtocol {
2424
func canTransition(from state: State, dueTo event: Event) -> Bool {
2525
switch event {
2626
case .joined:
27-
return configuration.heartbeatInterval > 0
27+
return true
2828
case .left:
2929
return true
3030
case .heartbeatSuccess:
@@ -104,7 +104,14 @@ fileprivate extension PresenceTransition {
104104
groups: joining.groups
105105
)
106106
if state is Presence.HeartbeatStopped {
107-
return TransitionResult(state: Presence.HeartbeatStopped(input: newInput))
107+
if configuration.heartbeatInterval > 0 {
108+
return TransitionResult(state: Presence.HeartbeatStopped(input: newInput))
109+
} else {
110+
// When heartbeat interval is 0 (no recurring heartbeat loop), we still need to
111+
// transition to Heartbeating state to ensure the server properly registers
112+
// presence on these channels with a one-time heartbeat call
113+
return TransitionResult(state: Presence.Heartbeating(input: newInput))
114+
}
108115
} else {
109116
return TransitionResult(state: Presence.Heartbeating(input: newInput))
110117
}
@@ -143,7 +150,13 @@ fileprivate extension PresenceTransition {
143150

144151
fileprivate extension PresenceTransition {
145152
func heartbeatSuccessTransition(from state: State) -> TransitionResult<State, Invocation> {
146-
return TransitionResult(state: Presence.HeartbeatCooldown(input: state.input))
153+
if configuration.heartbeatInterval > 0 {
154+
return TransitionResult(state: Presence.HeartbeatCooldown(input: state.input))
155+
} else {
156+
// When heartbeat interval is 0, stop heartbeating after successful registration
157+
// since no recurring heartbeat loop is needed
158+
return TransitionResult(state: Presence.HeartbeatStopped(input: state.input))
159+
}
147160
}
148161
}
149162

Sources/PubNub/EventEngine/Subscribe/Helpers/SubscribeRequest.swift

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,6 @@ class SubscribeRequest {
2222
private let channelStates: [String: JSONCodable]
2323
private var request: RequestReplaceable?
2424

25-
var retryLimit: UInt { configuration.automaticRetry?.retryLimit ?? 0 }
26-
var onAuthChallengeReceived: (() -> Void)?
27-
2825
init(
2926
configuration: PubNubConfiguration,
3027
channels: [String],
@@ -43,12 +40,6 @@ class SubscribeRequest {
4340
self.region = region
4441
self.session = session
4542
self.sessionResponseQueue = sessionResponseQueue
46-
47-
if let sessionListener = session.sessionStream as? SessionListener {
48-
sessionListener.sessionDidReceiveChallenge = { [weak self] _, _ in
49-
self?.onAuthChallengeReceived?()
50-
}
51-
}
5243
}
5344

5445
func execute(onCompletion: @escaping (Result<SubscribeResponse, PubNubError>) -> Void) {
@@ -83,7 +74,6 @@ class SubscribeRequest {
8374
}
8475

8576
func cancel() {
86-
onAuthChallengeReceived = nil
8777
request?.cancel(PubNubError(.clientCancelled))
8878
}
8979

Sources/PubNub/Helpers/Constants.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public enum Constant {
5757

5858
static let pubnubSwiftSDKName: String = "PubNubSwift"
5959

60-
static let pubnubSwiftSDKVersion: String = "9.2.0"
60+
static let pubnubSwiftSDKVersion: String = "9.2.1"
6161

6262
static let appBundleId: String = {
6363
if let info = Bundle.main.infoDictionary,

Sources/PubNub/PubNubConfiguration.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,8 +222,9 @@ public struct PubNubConfiguration: Hashable {
222222
/// This switch can help you verify the behavior of the PubNub SDK with the new engine enabled
223223
/// in your app. It will default to true in a future SDK release.
224224
public var enableEventEngine: Bool = true
225-
/// When `true` the SDK will resend the last channel state that was set using `PubNub.setPresence`.
226-
/// Applies only if `heartbeatInterval` is greater than 0 and `enableEventEngine` is true
225+
/// When `true` the SDK will resend the last channel state that was set using ``PubNub/setPresence(state:on:and:custom:completion:)``.
226+
///
227+
/// Applies only if `enableEventEngine` is true
227228
public var maintainPresenceState: Bool = false
228229
/// Reconnection policy which will be used if/when a request fails
229230
public var automaticRetry: AutomaticRetry?

Tests/PubNubTests/EventEngine/Presence/PresenceTransitionTests.swift

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@ class PresenceTransitionTests: XCTestCase {
6464
configuration: PubNubConfiguration(
6565
publishKey: "publishKey",
6666
subscribeKey: "subscribeKey",
67-
userId: "userId"
67+
userId: "userId",
68+
heartbeatInterval: 30
6869
)
6970
)
7071

@@ -87,7 +88,7 @@ class PresenceTransitionTests: XCTestCase {
8788
let state = Presence.HeartbeatInactive()
8889
let event = Presence.Event.joined(channels: ["c1", "c2"], groups: ["g1", "g2"])
8990

90-
XCTAssertFalse(PresenceTransition(configuration: configWithEmptyInterval).canTransition(from: state, dueTo: event))
91+
XCTAssertTrue(PresenceTransition(configuration: configWithEmptyInterval).canTransition(from: state, dueTo: event))
9192
XCTAssertTrue(PresenceTransition(configuration: configWithInterval).canTransition(from: state, dueTo: event))
9293
}
9394

@@ -106,6 +107,32 @@ class PresenceTransitionTests: XCTestCase {
106107
XCTAssertTrue(results.state.isEqual(to: expectedState))
107108
XCTAssertTrue(results.invocations.elementsEqual(expectedInvocations))
108109
}
110+
111+
func testPresence_JoinedEventForHeartbeatInactiveState_EmptyInterval() {
112+
let configWithEmptyInterval = PubNubConfiguration(
113+
publishKey: "pubKey",
114+
subscribeKey: "subKey",
115+
userId: "userId",
116+
heartbeatInterval: 0
117+
)
118+
119+
let presenceTransition = PresenceTransition(
120+
configuration: configWithEmptyInterval
121+
)
122+
let results = presenceTransition.transition(
123+
from: Presence.HeartbeatInactive(),
124+
event: .joined(channels: ["c3"], groups: ["g3"])
125+
)
126+
let expectedInvocations: [EffectInvocation<Presence.Invocation>] = [
127+
.regular(.heartbeat(channels: ["c3"], groups: ["g3"]))
128+
]
129+
let expectedState = Presence.Heartbeating(
130+
input: PresenceInput(channels: ["c3"], groups: ["g3"])
131+
)
132+
133+
XCTAssertTrue(results.state.isEqual(to: expectedState))
134+
XCTAssertTrue(results.invocations.elementsEqual(expectedInvocations))
135+
}
109136

110137
func testPresence_JoinedEventForHeartbeatingState() {
111138
let input = PresenceInput(
@@ -453,6 +480,26 @@ class PresenceTransitionTests: XCTestCase {
453480
XCTAssertTrue(results.invocations.elementsEqual(expectedInvocations))
454481
}
455482

483+
func testPresence_HeartbeatSuccessForEmptyInterval() {
484+
let configWithEmptyInterval = PubNubConfiguration(
485+
publishKey: "pubKey",
486+
subscribeKey: "subKey",
487+
userId: "userId",
488+
heartbeatInterval: 0
489+
)
490+
491+
let presenceTransition = PresenceTransition(configuration: configWithEmptyInterval)
492+
let input = PresenceInput(channels: ["c1", "c2"], groups: ["g1", "g2"])
493+
494+
let results = presenceTransition.transition(
495+
from: Presence.Heartbeating(input: input),
496+
event: .heartbeatSuccess
497+
)
498+
499+
XCTAssertTrue(results.state.isEqual(to: Presence.HeartbeatStopped(input: input)))
500+
XCTAssertTrue(results.invocations.isEmpty)
501+
}
502+
456503
// MARK: - Heartbeat Failed
457504

458505
func testPresence_HeartbeatFailedForHeartbeatingState() {

0 commit comments

Comments
 (0)