Skip to content
Draft
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
22 changes: 17 additions & 5 deletions Sources/MessagingInApp/Gist/Gist.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,13 @@ class Gist: GistProvider {
let subscriber = InAppMessageStoreSubscriber { state in
self.setupPollingAndFetch(skipMessageFetch: true, pollingInterval: state.pollInterval)
}
// Subscribe to changes in `pollInterval` property of `InAppMessageState`
inAppMessageManager.subscribe(keyPath: \.pollInterval, subscriber: subscriber)
// Subscribe to changes in `pollInterval` only - timer keeps running regardless of pause state
inAppMessageManager.subscribe(
comparator: { oldState, newState in
oldState.pollInterval == newState.pollInterval
},
subscriber: subscriber
)
return subscriber
}()
}
Expand Down Expand Up @@ -129,10 +134,10 @@ class Gist: GistProvider {
}

private func setupPollingAndFetch(skipMessageFetch: Bool, pollingInterval: Double) {
logger.logWithModuleTag("Setting up polling with interval: \(pollingInterval) seconds and skipMessageFetch: \(skipMessageFetch)", level: .info)
logger.logWithModuleTag("Setting up polling with interval: \(pollingInterval) seconds, skipMessageFetch: \(skipMessageFetch)", level: .info)
invalidateTimer()

// Timer must be scheduled on the main thread
// Always set up timer - it continues running even when in-app is paused
threadUtil.runMain {
self.queueTimer = Timer.scheduledTimer(
timeInterval: pollingInterval,
Expand All @@ -153,6 +158,7 @@ class Gist: GistProvider {
/// Fetches the user messages from the remote service and dispatches actions to the `InAppMessageManager`.
/// The method must be marked with `@objc` and public to be used as a selector in the `Timer` scheduled.
/// Also, the method must be called on main thread since it checks the application state.
/// When in-app messaging is paused, the timer continues to run but network requests are skipped.
@objc
func fetchUserMessages() {
logger.logWithModuleTag("Attempting to fetch user messages from remote service", level: .info)
Expand All @@ -161,10 +167,16 @@ class Gist: GistProvider {
return
}

logger.logWithModuleTag("Checking Gist queue service", level: .info)
inAppMessageManager.fetchState { [weak self] state in
guard let self else { return }

// Check if message fetching is paused before making network request
guard !state.isMessageFetchingPaused else {
logger.logWithModuleTag("Message fetching is paused, skipping network request", level: .info)
return
}

logger.logWithModuleTag("Checking Gist queue service", level: .info)
fetchUserQueue(state: state)
}
}
Expand Down
17 changes: 17 additions & 0 deletions Sources/MessagingInApp/MessagingInApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
func setEventListener(_ eventListener: InAppEventListener?)

func dismissMessage()

func pauseMessageFetching()

func resumeMessageFetching()
}

public class MessagingInApp: ModuleTopLevelObject<MessagingInAppInstance>, MessagingInAppInstance {
Expand Down Expand Up @@ -74,4 +78,17 @@
public func dismissMessage() {
implementation?.dismissMessage()
}

/// Pauses message fetching. The polling timer continues running but network requests
/// for new messages are suspended until message fetching is resumed. Messages already
/// in the queue will continue to be displayed.
public func pauseMessageFetching() {
implementation?.pauseMessageFetching()
}

Check warning on line 87 in Sources/MessagingInApp/MessagingInApp.swift

View check run for this annotation

Codecov / codecov/patch

Sources/MessagingInApp/MessagingInApp.swift#L85-L87

Added lines #L85 - L87 were not covered by tests

/// Resumes message fetching. If message fetching was previously paused,
/// this will restart the periodic fetching of new messages from the server.
public func resumeMessageFetching() {
implementation?.resumeMessageFetching()
}

Check warning on line 93 in Sources/MessagingInApp/MessagingInApp.swift

View check run for this annotation

Codecov / codecov/patch

Sources/MessagingInApp/MessagingInApp.swift#L91-L93

Added lines #L91 - L93 were not covered by tests
}
8 changes: 8 additions & 0 deletions Sources/MessagingInApp/MessagingInAppImplementation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,12 @@ class MessagingInAppImplementation: MessagingInAppInstance {
func dismissMessage() {
gist.dismissMessage()
}

func pauseMessageFetching() {
inAppMessageManager.dispatch(action: .pauseMessageFetching)
}

func resumeMessageFetching() {
inAppMessageManager.dispatch(action: .resumeMessageFetching)
}
}
8 changes: 8 additions & 0 deletions Sources/MessagingInApp/State/InAppMessageAction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import Foundation
enum InAppMessageAction: Equatable {
case initialize(siteId: String, dataCenter: String, environment: GistEnvironment)
case setPollingInterval(interval: Double)
case pauseMessageFetching
case resumeMessageFetching
case setUserIdentifier(user: String)
case setPageRoute(route: String)
case processMessageQueue(messages: [Message])
Expand Down Expand Up @@ -33,6 +35,12 @@ enum InAppMessageAction: Equatable {
case (.setPollingInterval(let lhsInterval), .setPollingInterval(let rhsInterval)):
return lhsInterval == rhsInterval

case (.pauseMessageFetching, .pauseMessageFetching):
return true

case (.resumeMessageFetching, .resumeMessageFetching):
return true

case (.setUserIdentifier(let lhsUser), .setUserIdentifier(let rhsUser)):
return lhsUser == rhsUser

Expand Down
4 changes: 3 additions & 1 deletion Sources/MessagingInApp/State/InAppMessageMiddleware.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ func userAuthenticationMiddleware() -> InAppMessageMiddleware {
case .initialize,
.setUserIdentifier,
.setPageRoute,
.resetState:
.resetState,
.pauseMessageFetching,
.resumeMessageFetching:
return next(action)

default:
Expand Down
6 changes: 6 additions & 0 deletions Sources/MessagingInApp/State/InAppMessageReducer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ private func reducer(action: InAppMessageAction, state: InAppMessageState) -> In
case .setPollingInterval(let interval):
return state.copy(pollInterval: interval)

case .pauseMessageFetching:
return state.copy(isMessageFetchingPaused: true)

case .resumeMessageFetching:
return state.copy(isMessageFetchingPaused: false)

case .setUserIdentifier(let user):
return state.copy(userId: user)

Expand Down
8 changes: 8 additions & 0 deletions Sources/MessagingInApp/State/InAppMessageState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
let dataCenter: String
let environment: GistEnvironment
let pollInterval: Double
let isMessageFetchingPaused: Bool
let userId: String?
let currentRoute: String?
let modalMessageState: ModalMessageState
Expand All @@ -21,6 +22,7 @@
dataCenter: String = "",
environment: GistEnvironment = .production,
pollInterval: Double = 600,
isMessageFetchingPaused: Bool = false,
userId: String? = nil,
currentRoute: String? = nil,
modalMessageState: ModalMessageState = .initial,
Expand All @@ -32,6 +34,7 @@
self.dataCenter = dataCenter
self.environment = environment
self.pollInterval = pollInterval
self.isMessageFetchingPaused = isMessageFetchingPaused
self.userId = userId
self.currentRoute = currentRoute
self.modalMessageState = modalMessageState
Expand All @@ -44,6 +47,7 @@
/// It is useful when updating state with only a few properties and keeping the rest as is.
func copy(
pollInterval: Double? = nil,
isMessageFetchingPaused: Bool? = nil,
userId: String? = nil,
currentRoute: String? = nil,
modalMessageState: ModalMessageState? = nil,
Expand All @@ -56,6 +60,7 @@
dataCenter: dataCenter,
environment: environment,
pollInterval: pollInterval ?? self.pollInterval,
isMessageFetchingPaused: isMessageFetchingPaused ?? self.isMessageFetchingPaused,
userId: userId ?? self.userId,
currentRoute: currentRoute ?? self.currentRoute,
modalMessageState: modalMessageState ?? self.modalMessageState,
Expand All @@ -70,6 +75,7 @@
lhs.dataCenter == rhs.dataCenter &&
lhs.environment == rhs.environment &&
lhs.pollInterval == rhs.pollInterval &&
lhs.isMessageFetchingPaused == rhs.isMessageFetchingPaused &&

Check warning on line 78 in Sources/MessagingInApp/State/InAppMessageState.swift

View check run for this annotation

Codecov / codecov/patch

Sources/MessagingInApp/State/InAppMessageState.swift#L78

Added line #L78 was not covered by tests
lhs.userId == rhs.userId &&
lhs.currentRoute == rhs.currentRoute &&
lhs.modalMessageState == rhs.modalMessageState &&
Expand All @@ -84,6 +90,7 @@
dataCenter: '\(dataCenter)',
environment: \(environment),
pollInterval: \(pollInterval),
isMessageFetchingPaused: \(isMessageFetchingPaused),
userId: \(String(describing: userId)),
currentRoute: \(String(describing: currentRoute)),
modalMessageState: \(modalMessageState),
Expand Down Expand Up @@ -114,6 +121,7 @@
putIfDifferent(\.dataCenter, as: "dataCenter")
putIfDifferent(\.environment, as: "environment")
putIfDifferent(\.pollInterval, as: "pollInterval")
putIfDifferent(\.isMessageFetchingPaused, as: "isMessageFetchingPaused")
putIfDifferent(\.userId, as: "userId")
putIfDifferent(\.currentRoute, as: "currentRoute")
putIfDifferent(\.modalMessageState, as: "currentMessageState")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -869,6 +869,12 @@ public class MessagingInAppInstanceMock: MessagingInAppInstance, Mock {
mockCalled = false // do last as resetting properties above can make this true
dismissMessageCallsCount = 0

mockCalled = false // do last as resetting properties above can make this true
pauseMessageFetchingCallsCount = 0

mockCalled = false // do last as resetting properties above can make this true
resumeMessageFetchingCallsCount = 0

mockCalled = false // do last as resetting properties above can make this true
}

Expand Down Expand Up @@ -919,6 +925,48 @@ public class MessagingInAppInstanceMock: MessagingInAppInstance, Mock {
dismissMessageCallsCount += 1
dismissMessageClosure?()
}

// MARK: - pauseMessageFetching

/// Number of times the function was called.
@Atomic public private(set) var pauseMessageFetchingCallsCount = 0
/// `true` if the function was ever called.
public var pauseMessageFetchingCalled: Bool {
pauseMessageFetchingCallsCount > 0
}

/**
Set closure to get called when function gets called. Great way to test logic or return a value for the function.
*/
public var pauseMessageFetchingClosure: (() -> Void)?

/// Mocked function for `pauseMessageFetching()`. Your opportunity to return a mocked value and check result of mock in test code.
public func pauseMessageFetching() {
mockCalled = true
pauseMessageFetchingCallsCount += 1
pauseMessageFetchingClosure?()
}

// MARK: - resumeMessageFetching

/// Number of times the function was called.
@Atomic public private(set) var resumeMessageFetchingCallsCount = 0
/// `true` if the function was ever called.
public var resumeMessageFetchingCalled: Bool {
resumeMessageFetchingCallsCount > 0
}

/**
Set closure to get called when function gets called. Great way to test logic or return a value for the function.
*/
public var resumeMessageFetchingClosure: (() -> Void)?

/// Mocked function for `resumeMessageFetching()`. Your opportunity to return a mocked value and check result of mock in test code.
public func resumeMessageFetching() {
mockCalled = true
resumeMessageFetchingCallsCount += 1
resumeMessageFetchingClosure?()
}
}

// swiftlint:enable all
Loading
Loading