From 266122d841cae1267be10fc61bdb59734b97e888 Mon Sep 17 00:00:00 2001 From: Rehan Date: Mon, 2 Sep 2024 22:33:53 +0500 Subject: [PATCH] chore: gist classes refactor --- Sources/MessagingInApp/Gist/Gist.swift | 208 +++--------------- .../Gist/Managers/MessageManager.swift | 114 ++++++---- .../Gist/Managers/MessageQueueManager.swift | 181 ++++++--------- .../Gist/Managers/QueueManager.swift | 40 ++-- .../MessagingInApp/Gist/Views/GistView.swift | 3 +- .../MessagingInAppImplementation.swift | 2 +- .../AutoDependencyInjection.generated.swift | 81 +++++++ 7 files changed, 277 insertions(+), 352 deletions(-) diff --git a/Sources/MessagingInApp/Gist/Gist.swift b/Sources/MessagingInApp/Gist/Gist.swift index aebf248e1..345ddb77e 100644 --- a/Sources/MessagingInApp/Gist/Gist.swift +++ b/Sources/MessagingInApp/Gist/Gist.swift @@ -2,195 +2,59 @@ import CioInternalCommon import Foundation import UIKit -public class Gist: GistDelegate { - private let logger: Logger - var messageQueueManager = MessageQueueManager() - var shownMessageQueueIds: Set = [] - private var messageManagers: [MessageManager] = [] - public var siteId: String = "" - public var dataCenter: String = "" - - public weak var delegate: GistDelegate? - - public static let shared = Gist() - - init() { - self.logger = DIGraphShared.shared.logger - } - - public func setup( - siteId: String, - dataCenter: String, - logging: Bool = false, - env: GistEnvironment = .production +// sourcery: InjectRegisterShared = "Gist" +// sourcery: InjectSingleton +public class Gist { + private let gistDelegate: GistDelegate + private let inAppMessageManager: InAppMessageManager + private let messageQueueManager: MessageQueueManager + + init( + gistDelegate: GistDelegate, + inAppMessageManager: InAppMessageManager, + messageQueueManager: MessageQueueManager ) { - self.siteId = siteId - self.dataCenter = dataCenter - messageQueueManager.setup() - - // Initialising Gist web with an empty message to fetch fonts and other assets. - _ = Gist.shared.getMessageView(Message(messageId: "")) - } - - // For testing to reset the singleton state - func reset() { - clearUserToken() - messageQueueManager = MessageQueueManager() - messageManagers = [] - // RouteManager.clearCurrentRoute() - } - - public func resetState() {} - - public func setEventListener(_ eventListener: (any InAppEventListener)?) {} - - // MARK: User - - public func setUserToken(_ userToken: String) { - // UserManager().setUserToken(userToken: userToken) + self.gistDelegate = gistDelegate + self.inAppMessageManager = inAppMessageManager + self.messageQueueManager = messageQueueManager } - public func clearUserToken() { - cancelModalMessage(ifDoesNotMatchRoute: "") // provide a new route to trigger a modal cancel. - messageQueueManager.clearLocalStore() - // UserManager().clearUserToken() - messageQueueManager.clearUserMessagesFromLocalStore() + public func resetState() { + inAppMessageManager.dispatch(action: .resetState) } - // MARK: Route - - public func getCurrentRoute() -> String { - // RouteManager.getCurrentRoute() - "" - } - - public func setCurrentRoute(_ currentRoute: String) { -// if RouteManager.getCurrentRoute() == currentRoute { -// return // ignore request, route has not changed. -// } - - cancelModalMessage(ifDoesNotMatchRoute: currentRoute) - - // RouteManager.setCurrentRoute(currentRoute) - messageQueueManager.fetchUserMessagesFromLocalStore() - } - - public func clearCurrentRoute() { - // RouteManager.clearCurrentRoute() + func setEventListener(_ eventListener: InAppEventListener?) { + gistDelegate.setEventListener(eventListener) } - // MARK: Message Actions - - public func showMessage(_ message: Message, position: MessagePosition = .center) -> Bool { - if let messageManager = getModalMessageManager() { - logger.info("Message cannot be displayed, \(messageManager.currentMessage.messageId) is being displayed.") - } else { - let messageManager = createMessageManager(siteId: siteId, message: message) - messageManager.showMessage(position: position) - return true - } - return false - } - - public func getMessageView(_ message: Message) -> GistView { - let messageManager = createMessageManager(siteId: siteId, message: message) - return messageManager.getMessageView() - } + public func setUserToken(_ userToken: String) { + inAppMessageManager.fetchState { [self] state in + if state.userId == userToken { + return + } - public func dismissMessage(instanceId: String? = nil, completionHandler: (() -> Void)? = nil) { - if let id = instanceId, let messageManager = messageManager(instanceId: id) { - messageManager.removePersistentMessage() - messageManager.dismissMessage(completionHandler: completionHandler) - } else { - getModalMessageManager()?.dismissMessage(completionHandler: completionHandler) + inAppMessageManager.dispatch(action: .setUserIdentifier(user: userToken)) + messageQueueManager.setupPollingAndFetch(skipMessageFetch: false, pollingInterval: state.pollInterval) } } - // MARK: Events + public func setCurrentRoute(_ currentRoute: String) { + inAppMessageManager.fetchState { [self] state in + if state.currentRoute == currentRoute { + return // ignore request, route has not changed. + } - public func messageShown(message: Message) { - logger.debug("Message with route: \(message.messageId) shown") - if message.gistProperties.persistent != true { - logMessageView(message: message) - } else { - logger.debug("Persistent message shown, skipping logging view") + inAppMessageManager.dispatch(action: .setPageRoute(route: currentRoute)) } - delegate?.messageShown(message: message) - } - - public func messageDismissed(message: Message) { - logger.debug("Message with id: \(message.messageId) dismissed") - removeMessageManager(instanceId: message.instanceId) - delegate?.messageDismissed(message: message) - - messageQueueManager.fetchUserMessagesFromLocalStore() - } - - public func messageError(message: Message) { - removeMessageManager(instanceId: message.instanceId) - delegate?.messageError(message: message) - } - - public func action(message: Message, currentRoute: String, action: String, name: String) { - delegate?.action(message: message, currentRoute: currentRoute, action: action, name: name) } - public func embedMessage(message: Message, elementId: String) { - delegate?.embedMessage(message: message, elementId: elementId) - } - - func logMessageView(message: Message) { - messageQueueManager.removeMessageFromLocalStore(message: message) - if let queueId = message.queueId { - shownMessageQueueIds.insert(queueId) - } - let state = InAppMessageState() - DIGraphShared.shared.logManager - .logView(state: state, message: message) { response in - if case .failure(let error) = response { - self.logger.error("Failed to log view for message: \(message.messageId) with error: \(error)") - } + public func dismissMessage() { + inAppMessageManager.fetchState { [self] state in + guard case .displayed(let message) = state.currentMessageState else { + return } - } - // When the user navigates to a different screen, modal messages should only appear if they are meant for the current screen. - // If the currently displayed/loading modal message has a page rule, it should not be shown anymore. - private func cancelModalMessage(ifDoesNotMatchRoute newRoute: String) { - if let messageManager = getModalMessageManager() { - let modalMessageLoadingOrDisplayed = messageManager.currentMessage - - if modalMessageLoadingOrDisplayed.doesHavePageRule(), !modalMessageLoadingOrDisplayed.doesPageRuleMatch(route: newRoute) { - // the page rule has changed and the currently loading/visible modal has page rules set, it should no longer be shown. - logger.debug("Cancelled showing message with id: \(modalMessageLoadingOrDisplayed.messageId)") - - // Stop showing the current message synchronously meaning to remove from UI instantly. - // We want to be sure the message is gone when this function returns and be ready to display another message if needed. - messageManager.cancelShowingMessage() - - // Removing the message manager allows you to show a new modal message. Otherwise, request to show will be ignored. - removeMessageManager(instanceId: modalMessageLoadingOrDisplayed.instanceId) - } + inAppMessageManager.dispatch(action: .dismissMessage(message: message)) } } - - // Message Manager - - private func createMessageManager(siteId: String, message: Message) -> MessageManager { - let messageManager = MessageManager(state: InAppMessageState(), message: message) - messageManager.delegate = self - messageManagers.append(messageManager) - return messageManager - } - - func getModalMessageManager() -> MessageManager? { - messageManagers.first(where: { !$0.isMessageEmbed }) - } - - func messageManager(instanceId: String) -> MessageManager? { - messageManagers.first(where: { $0.currentMessage.instanceId == instanceId }) - } - - func removeMessageManager(instanceId: String) { - messageManagers.removeAll(where: { $0.currentMessage.instanceId == instanceId }) - } } diff --git a/Sources/MessagingInApp/Gist/Managers/MessageManager.swift b/Sources/MessagingInApp/Gist/Managers/MessageManager.swift index 7f027fdf2..c252facec 100644 --- a/Sources/MessagingInApp/Gist/Managers/MessageManager.swift +++ b/Sources/MessagingInApp/Gist/Managers/MessageManager.swift @@ -8,30 +8,33 @@ public enum GistMessageActions: String { class MessageManager: EngineWebDelegate { private let logger: Logger - private var engine: EngineWebInstance - private let siteId: String - private var messagePosition: MessagePosition = .top - var messageLoaded = false - private var modalViewManager: ModalViewManager? - var isMessageEmbed = false - let currentMessage: Message - var gistView: GistView! + private let inAppMessageManager: InAppMessageManager + + private let currentMessage: Message private var currentRoute: String + private var isMessageEmbed: Bool = false + private var messagePosition: MessagePosition = .center + + private var inAppMessageStoreSubscriber: InAppMessageStoreSubscriber? private var elapsedTimer = ElapsedTimer() - weak var delegate: GistDelegate? - private let engineWebProvider: EngineWebProvider = DIGraphShared.shared.engineWebProvider + private var modalViewManager: ModalViewManager? + private var engine: EngineWebInstance! + private var gistView: GistView! + private let engineWebProvider: EngineWebProvider init(state: InAppMessageState, message: Message) { - self.siteId = state.siteId self.currentMessage = message self.currentRoute = message.messageId + self.isMessageEmbed = !(message.gistProperties.elementId?.isBlankOrEmpty() ?? true) let diGraph = DIGraphShared.shared self.logger = diGraph.logger + self.inAppMessageManager = diGraph.inAppMessageManager + self.engineWebProvider = diGraph.engineWebProvider let engineWebConfiguration = EngineWebConfiguration( - siteId: Gist.shared.siteId, - dataCenter: Gist.shared.dataCenter, + siteId: state.siteId, + dataCenter: state.dataCenter, instanceId: message.instanceId, endpoint: state.environment.networkSettings.engineAPI, messageId: message.messageId, @@ -41,6 +44,32 @@ class MessageManager: EngineWebDelegate { self.engine = engineWebProvider.getEngineWebInstance(configuration: engineWebConfiguration, state: state) engine.delegate = self self.gistView = GistView(message: currentMessage, engineView: engine.view) + + subscribeToInAppMessageState() + } + + func subscribeToInAppMessageState() { + inAppMessageStoreSubscriber = InAppMessageStoreSubscriber { [self] state in + switch state.currentMessageState { + case .displayed: + DispatchQueue.main.async { + self.loadModalMessage() + } + + case .dismissed: + DispatchQueue.main.async { + self.dismissMessage { + self.inAppMessageStoreSubscriber = nil + } + } + + default: + break + } + } + if let subscriber = inAppMessageStoreSubscriber { + inAppMessageManager.subscribe(keyPath: \.currentMessageState, subscriber: subscriber) + } } var isShowingMessage: Bool { @@ -62,13 +91,11 @@ class MessageManager: EngineWebDelegate { } private func loadModalMessage() { - if messageLoaded { - modalViewManager = ModalViewManager(gistView: gistView, position: messagePosition) - modalViewManager?.showModalView { [weak self] in - guard let self = self else { return } - self.delegate?.messageShown(message: self.currentMessage) - self.elapsedTimer.end() - } + modalViewManager = ModalViewManager(gistView: gistView, position: messagePosition) + modalViewManager?.showModalView { [weak self] in + guard let self = self else { return } + + self.elapsedTimer.end() } } @@ -82,23 +109,16 @@ class MessageManager: EngineWebDelegate { modalViewManager.cancel() } - func dismissMessage(completionHandler: (() -> Void)? = nil) { + private func dismissMessage(completionHandler: (() -> Void)? = nil) { if let modalViewManager = modalViewManager { modalViewManager.dismissModalView { [weak self] in - guard let self = self else { return } - self.delegate?.messageDismissed(message: self.currentMessage) + guard let _ = self else { return } + completionHandler?() } } } - func removePersistentMessage() { - if currentMessage.gistProperties.persistent == true { - logger.debug("Persistent message dismissed, logging view") - Gist.shared.logMessageView(message: currentMessage) - } - } - func bootstrapped() { logger.debug("Bourbon Engine bootstrapped") @@ -111,15 +131,14 @@ class MessageManager: EngineWebDelegate { // swiftlint:disable cyclomatic_complexity func tap(name: String, action: String, system: Bool) { logger.info("Action triggered: \(action) with name: \(name)") - delegate?.action(message: currentMessage, currentRoute: currentRoute, action: action, name: name) + inAppMessageManager.dispatch(action: .engineAction(action: .tap(message: currentMessage, route: currentRoute, name: name, action: action))) gistView.delegate?.action(message: currentMessage, currentRoute: currentRoute, action: action, name: name) if let url = URL(string: action), url.scheme == "gist" { switch url.host { case "close": logger.info("Dismissing from action: \(action)") - removePersistentMessage() - dismissMessage() + inAppMessageManager.dispatch(action: .dismissMessage(message: currentMessage, viaCloseAction: true)) case "loadPage": if let page = url.queryParameters?["url"], let pageUrl = URL(string: page), @@ -130,9 +149,8 @@ class MessageManager: EngineWebDelegate { if currentMessage.isEmbedded { showNewMessage(url: url) } else { - dismissMessage { - self.showNewMessage(url: url) - } + inAppMessageManager.dispatch(action: .dismissMessage(message: currentMessage, shouldLog: false)) + showNewMessage(url: url) } default: break } @@ -162,14 +180,14 @@ class MessageManager: EngineWebDelegate { UIApplication.shared.open(url) { handled in if handled { self.logger.info("Dismissing from system action: \(action)") - self.dismissMessage() + self.inAppMessageManager.dispatch(action: .dismissMessage(message: self.currentMessage, shouldLog: false)) } else { self.logger.info("System action not handled") } } } else { logger.info("Handled by NSUserActivity") - dismissMessage() + inAppMessageManager.dispatch(action: .dismissMessage(message: currentMessage)) } } } @@ -218,33 +236,36 @@ class MessageManager: EngineWebDelegate { func routeError(route: String) { logger.error("Error loading message with route: \(route)") - delegate?.messageError(message: currentMessage) + inAppMessageManager.dispatch(action: .engineAction(action: .messageLoadingFailed(message: currentMessage))) } func error() { logger.error("Error loading message with id: \(currentMessage.messageId)") - delegate?.messageError(message: currentMessage) + inAppMessageManager.dispatch(action: .engineAction(action: .error(message: currentMessage))) } func routeLoaded(route: String) { logger.info("Message loaded with route: \(route)") currentRoute = route - if route == currentMessage.messageId, !messageLoaded { - messageLoaded = true + if route == currentMessage.messageId { if isMessageEmbed { - delegate?.messageShown(message: currentMessage) + inAppMessageManager.dispatch(action: .displayMessage(message: currentMessage)) } else { if UIApplication.shared.applicationState == .active { - loadModalMessage() + inAppMessageManager.dispatch(action: .displayMessage(message: currentMessage)) } else { - Gist.shared.removeMessageManager(instanceId: currentMessage.instanceId) + inAppMessageManager.dispatch(action: .dismissMessage(message: currentMessage, shouldLog: false, viaCloseAction: false)) } } } } deinit { + if let subscriber = inAppMessageStoreSubscriber { + inAppMessageManager.unsubscribe(subscriber: subscriber) + } + inAppMessageStoreSubscriber = nil engine.cleanEngineWeb() } @@ -259,7 +280,8 @@ class MessageManager: EngineWebDelegate { } if let messageId = url.queryParameters?["messageId"] { - _ = Gist.shared.showMessage(Message(messageId: messageId, properties: properties)) + let message = Message(messageId: messageId, properties: properties) + inAppMessageManager.dispatch(action: .loadMessage(message: message)) } } diff --git a/Sources/MessagingInApp/Gist/Managers/MessageQueueManager.swift b/Sources/MessagingInApp/Gist/Managers/MessageQueueManager.swift index 4d6ece52c..efd238078 100644 --- a/Sources/MessagingInApp/Gist/Managers/MessageQueueManager.swift +++ b/Sources/MessagingInApp/Gist/Managers/MessageQueueManager.swift @@ -1,152 +1,113 @@ import CioInternalCommon import Foundation +import ReSwift import UIKit +// sourcery: InjectRegisterShared = "MessageQueueManager" +// sourcery: InjectSingleton class MessageQueueManager { private let logger: Logger - var interval: Double = 600 + private let inAppMessageManager: InAppMessageManager + private let queueManager: QueueManager + private let threadUtil: ThreadUtil + + private var inAppMessageStoreSubscriber: InAppMessageStoreSubscriber? private var queueTimer: Timer? - // The local message store is used to keep messages that can't be displayed because the route rule doesnt match. - var localMessageStore: [String: Message] = [:] - init() { - self.logger = DIGraphShared.shared.logger + init(logger: Logger, inAppMessageManager: InAppMessageManager, queueManager: QueueManager, threadUtil: ThreadUtil) { + self.logger = logger + self.inAppMessageManager = inAppMessageManager + self.queueManager = queueManager + self.threadUtil = threadUtil + + subscribeToInAppMessageState() } - func setup(skipQueueCheck: Bool = false) { + private func subscribeToInAppMessageState() { + inAppMessageStoreSubscriber = InAppMessageStoreSubscriber { [weak self] state in + guard let self else { return } + + let newPollingInterval = state.pollInterval + self.setupPollingAndFetch(skipMessageFetch: true, pollingInterval: newPollingInterval) + } + if let subscriber = inAppMessageStoreSubscriber { + inAppMessageManager.subscribe(keyPath: \.pollInterval, subscriber: subscriber) + } + } + + func setupPollingAndFetch(skipMessageFetch: Bool, pollingInterval: Double) { queueTimer?.invalidate() queueTimer = nil - queueTimer = Timer.scheduledTimer( - timeInterval: interval, - target: self, - selector: #selector(fetchUserMessages), - userInfo: nil, - repeats: true - ) + threadUtil.runMain { + self.queueTimer = Timer.scheduledTimer( + timeInterval: pollingInterval, + target: self, + selector: #selector(self.fetchUserMessages), + userInfo: nil, + repeats: true + ) + } - if !skipQueueCheck { + if !skipMessageFetch { // Since on app launch there's a short period where the applicationState is still set to "background" // We wait 1 second for the app to become active before checking for messages. - DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + threadUtil.runMainAfterDelay(deadline: .now() + 1) { self.fetchUserMessages() } } } - func clearLocalStore() { - localMessageStore = [:] - QueueManager(siteId: Gist.shared.siteId, dataCenter: Gist.shared.dataCenter).clearCachedUserQueue() - } - deinit { - queueTimer?.invalidate() - } - - func fetchUserMessagesFromLocalStore() { - logger.info("Checking local store with \(localMessageStore.count) messages") - let sortedMessages = localMessageStore.sorted { - switch ($0.value.priority, $1.value.priority) { - case (let priority0?, let priority1?): - // Both messages have a priority, so we compare them. - return priority0 < priority1 - case (nil, _): - // The first message has no priority, it should be considered greater so that it ends up at the end of the sorted array. - return false - case (_, nil): - // The second message has no priority, the first message should be ordered first. - return true - } - } - sortedMessages.forEach { message in - showMessageIfMeetsCriteria(message: message.value) - } - } - - func clearUserMessagesFromLocalStore() { - localMessageStore.removeAll() - } - - func removeMessageFromLocalStore(message: Message) { - guard let queueId = message.queueId else { - return - } - localMessageStore.removeValue(forKey: queueId) - } - - func addMessagesToLocalStore(messages: [Message]) { - messages.forEach { message in - guard let queueId = message.queueId else { - return - } - localMessageStore.updateValue(message, forKey: queueId) + if let subscriber = inAppMessageStoreSubscriber { + inAppMessageManager.unsubscribe(subscriber: subscriber) } + inAppMessageStoreSubscriber = nil + queueTimer?.invalidate() } @objc func fetchUserMessages() { - let state = InAppMessageState() - if UIApplication.shared.applicationState != .background { - logger.info("Checking Gist queue service") - if let userToken = state.userId { - QueueManager(siteId: Gist.shared.siteId, dataCenter: Gist.shared.dataCenter) - .fetchUserQueue(state: state, completionHandler: { response in - switch response { - case .success(nil): - self.logger.info("No changes to remote queue") - case .success(let responses): - guard let responses else { - return - } - - self.logger.info("Gist queue service found \(responses.count) new messages") - - self.processFetchResponse(responses.map { $0.toMessage() }) - case .failure(let error): - self.logger.error("Error fetching messages from Gist queue service. \(error.localizedDescription)") - } - }) - } else { - logger.debug("User token not set, skipping fetch user queue.") - } - } else { + logger.info("fetchUserMessages called") + guard UIApplication.shared.applicationState != .background else { logger.info("Application in background, skipping queue check.") + return } - } - func processFetchResponse(_ fetchedMessages: [Message]) { - // To prevent us from showing expired / revoked messages, reset the local queue with the latest queue from the backend service. - // The backend service is the single-source-of-truth for in-app messages for each user. - clearUserMessagesFromLocalStore() - addMessagesToLocalStore(messages: fetchedMessages) + logger.info("Checking Gist queue service") + inAppMessageManager.fetchState { [weak self] state in + guard let self else { return } - for message in fetchedMessages { - showMessageIfMeetsCriteria(message: message) + fetchUserQueue(state: state) } } - private func showMessageIfMeetsCriteria(message: Message) { - // Skip shown messages - if let queueId = message.queueId, Gist.shared.shownMessageQueueIds.contains(queueId) { - logger.info("Message with queueId: \(queueId) already shown, skipping.") + private func fetchUserQueue(state: InAppMessageState) { + guard let _ = state.userId else { + logger.debug("User token not set, skipping fetch user queue.") return } - let position = message.gistProperties.position + threadUtil.runBackground { + self.queueManager.fetchUserQueue(state: state) { [weak self] response in + guard let self else { return } - if message.doesHavePageRule(), let cleanPageRule = message.cleanPageRule { - if !message.doesPageRuleMatch(route: Gist.shared.getCurrentRoute()) { - logger.debug("Current route is \(Gist.shared.getCurrentRoute()), needed \(cleanPageRule)") - return // exit early to not show the message since page rule doesnt match - } - } + switch response { + case .success(nil): + logger.info("No changes to remote queue") + inAppMessageManager.dispatch(action: .clearMessageQueue) - if let elementId = message.gistProperties.elementId { - logger.info("Embedding message with Element Id \(elementId)") - Gist.shared.embedMessage(message: message, elementId: elementId) - return - } else { - _ = Gist.shared.showMessage(message, position: position) + case .success(let responses): + guard let responses else { return } + + logger.info("Gist queue service found \(responses.count) new messages") + inAppMessageManager.dispatch(action: .processMessageQueue(messages: responses.map { $0.toMessage() })) + + case .failure(let error): + logger.error("Error fetching messages from Gist queue service. \(error.localizedDescription)") + inAppMessageManager.dispatch(action: .clearMessageQueue) + } + } } } } diff --git a/Sources/MessagingInApp/Gist/Managers/QueueManager.swift b/Sources/MessagingInApp/Gist/Managers/QueueManager.swift index 21c424d11..dec8fb775 100644 --- a/Sources/MessagingInApp/Gist/Managers/QueueManager.swift +++ b/Sources/MessagingInApp/Gist/Managers/QueueManager.swift @@ -1,12 +1,12 @@ import CioInternalCommon import Foundation +// sourcery: InjectRegisterShared = "QueueManager" +// sourcery: InjectSingleton class QueueManager { - let siteId: String - let dataCenter: String - var keyValueStore: SharedKeyValueStorage = DIGraphShared.shared.sharedKeyValueStorage - let gistQueueNetwork: GistQueueNetwork = DIGraphShared.shared.gistQueueNetwork - let threadUtil: ThreadUtil = DIGraphShared.shared.threadUtil + private var keyValueStore: SharedKeyValueStorage + private let gistQueueNetwork: GistQueueNetwork + private let inAppMessageManager: InAppMessageManager private var cachedFetchUserQueueResponse: Data? { get { @@ -17,9 +17,10 @@ class QueueManager { } } - init(siteId: String, dataCenter: String) { - self.siteId = siteId - self.dataCenter = dataCenter + init(keyValueStore: SharedKeyValueStorage, gistQueueNetwork: GistQueueNetwork, inAppMessageManager: InAppMessageManager) { + self.keyValueStore = keyValueStore + self.gistQueueNetwork = gistQueueNetwork + self.inAppMessageManager = inAppMessageManager } func clearCachedUserQueue() { @@ -41,9 +42,7 @@ class QueueManager { do { let userQueue = try self.parseResponseBody(lastCachedResponse) - self.threadUtil.runMain { - completionHandler(.success(userQueue)) - } + completionHandler(.success(userQueue)) } catch { completionHandler(.failure(error)) } @@ -53,9 +52,7 @@ class QueueManager { self.cachedFetchUserQueueResponse = data - self.threadUtil.runMain { - completionHandler(.success(userQueue)) - } + completionHandler(.success(userQueue)) } catch { completionHandler(.failure(error)) } @@ -82,14 +79,13 @@ class QueueManager { } private func updatePollingInterval(headers: [AnyHashable: Any]) { - if let newPollingIntervalString = headers["x-gist-queue-polling-interval"] as? String, - let newPollingInterval = Double(newPollingIntervalString), - newPollingInterval != Gist.shared.messageQueueManager.interval { - DispatchQueue.main.async { - Gist.shared.messageQueueManager.interval = newPollingInterval - Gist.shared.messageQueueManager.setup(skipQueueCheck: true) - DIGraphShared.shared.logger.info("Polling interval changed to: \(newPollingInterval) seconds") - } + guard let newPollingIntervalString = headers["x-gist-queue-polling-interval"] as? String, + let newPollingInterval = Double(newPollingIntervalString) else { return } + + inAppMessageManager.fetchState { [weak self] state in + guard let self = self, newPollingInterval != state.pollInterval else { return } + + inAppMessageManager.dispatch(action: .setPollingInterval(interval: newPollingInterval)) } } } diff --git a/Sources/MessagingInApp/Gist/Views/GistView.swift b/Sources/MessagingInApp/Gist/Views/GistView.swift index 7e27b7c6a..4cc1b0076 100644 --- a/Sources/MessagingInApp/Gist/Views/GistView.swift +++ b/Sources/MessagingInApp/Gist/Views/GistView.swift @@ -1,3 +1,4 @@ +import CioInternalCommon import Foundation import UIKit @@ -20,7 +21,7 @@ public class GistView: UIView { override public func removeFromSuperview() { super.removeFromSuperview() if let message = message { - Gist.shared.removeMessageManager(instanceId: message.instanceId) + DIGraphShared.shared.gist.dismissMessage() } } } diff --git a/Sources/MessagingInApp/MessagingInAppImplementation.swift b/Sources/MessagingInApp/MessagingInAppImplementation.swift index 8de169711..2b4f7f1ff 100644 --- a/Sources/MessagingInApp/MessagingInAppImplementation.swift +++ b/Sources/MessagingInApp/MessagingInAppImplementation.swift @@ -14,7 +14,7 @@ class MessagingInAppImplementation: MessagingInAppInstance { self.moduleConfig = moduleConfig self.logger = diGraph.logger self.inAppMessageManager = diGraph.inAppMessageManager - self.gist = Gist.shared + self.gist = diGraph.gist self.threadUtil = diGraph.threadUtil self.eventBusHandler = diGraph.eventBusHandler diff --git a/Sources/MessagingInApp/autogenerated/AutoDependencyInjection.generated.swift b/Sources/MessagingInApp/autogenerated/AutoDependencyInjection.generated.swift index 09d766ec4..4ba814eab 100644 --- a/Sources/MessagingInApp/autogenerated/AutoDependencyInjection.generated.swift +++ b/Sources/MessagingInApp/autogenerated/AutoDependencyInjection.generated.swift @@ -58,6 +58,9 @@ extension DIGraphShared { _ = engineWebProvider countDependenciesResolved += 1 + _ = gist + countDependenciesResolved += 1 + _ = gistDelegate countDependenciesResolved += 1 @@ -70,6 +73,12 @@ extension DIGraphShared { _ = logManager countDependenciesResolved += 1 + _ = messageQueueManager + countDependenciesResolved += 1 + + _ = queueManager + countDependenciesResolved += 1 + return countDependenciesResolved } @@ -84,6 +93,30 @@ extension DIGraphShared { EngineWebProviderImpl() } + // Gist (singleton) + public var gist: Gist { + getOverriddenInstance() ?? + sharedGist + } + + public var sharedGist: Gist { + // Use a DispatchQueue to make singleton thread safe. You must create unique dispatchqueues instead of using 1 shared one or you will get a crash when trying + // to call DispatchQueue.sync{} while already inside another DispatchQueue.sync{} call. + DispatchQueue(label: "DIGraphShared_Gist_singleton_access").sync { + if let overridenDep: Gist = getOverriddenInstance() { + return overridenDep + } + let existingSingletonInstance = self.singletons[String(describing: Gist.self)] as? Gist + let instance = existingSingletonInstance ?? _get_gist() + self.singletons[String(describing: Gist.self)] = instance + return instance + } + } + + private func _get_gist() -> Gist { + Gist(gistDelegate: gistDelegate, inAppMessageManager: inAppMessageManager, messageQueueManager: messageQueueManager) + } + // GistDelegate (singleton) var gistDelegate: GistDelegate { getOverriddenInstance() ?? @@ -151,6 +184,54 @@ extension DIGraphShared { private var newLogManager: LogManager { LogManager(gistQueueNetwork: gistQueueNetwork) } + + // MessageQueueManager (singleton) + var messageQueueManager: MessageQueueManager { + getOverriddenInstance() ?? + sharedMessageQueueManager + } + + var sharedMessageQueueManager: MessageQueueManager { + // Use a DispatchQueue to make singleton thread safe. You must create unique dispatchqueues instead of using 1 shared one or you will get a crash when trying + // to call DispatchQueue.sync{} while already inside another DispatchQueue.sync{} call. + DispatchQueue(label: "DIGraphShared_MessageQueueManager_singleton_access").sync { + if let overridenDep: MessageQueueManager = getOverriddenInstance() { + return overridenDep + } + let existingSingletonInstance = self.singletons[String(describing: MessageQueueManager.self)] as? MessageQueueManager + let instance = existingSingletonInstance ?? _get_messageQueueManager() + self.singletons[String(describing: MessageQueueManager.self)] = instance + return instance + } + } + + private func _get_messageQueueManager() -> MessageQueueManager { + MessageQueueManager(logger: logger, inAppMessageManager: inAppMessageManager, queueManager: queueManager, threadUtil: threadUtil) + } + + // QueueManager (singleton) + var queueManager: QueueManager { + getOverriddenInstance() ?? + sharedQueueManager + } + + var sharedQueueManager: QueueManager { + // Use a DispatchQueue to make singleton thread safe. You must create unique dispatchqueues instead of using 1 shared one or you will get a crash when trying + // to call DispatchQueue.sync{} while already inside another DispatchQueue.sync{} call. + DispatchQueue(label: "DIGraphShared_QueueManager_singleton_access").sync { + if let overridenDep: QueueManager = getOverriddenInstance() { + return overridenDep + } + let existingSingletonInstance = self.singletons[String(describing: QueueManager.self)] as? QueueManager + let instance = existingSingletonInstance ?? _get_queueManager() + self.singletons[String(describing: QueueManager.self)] = instance + return instance + } + } + + private func _get_queueManager() -> QueueManager { + QueueManager(keyValueStore: sharedKeyValueStorage, gistQueueNetwork: gistQueueNetwork, inAppMessageManager: inAppMessageManager) + } } // swiftlint:enable all