openreplay/tracker/tracker-ios/Sources/ORTracker/Managers/MessageCollector.swift
Delirium c0da34c528
feat(ios): add Ios source (#1640)
* feat: add ios and rn source

* fix(ios): remove testing keys

* fix(tracker): change default path
2023-11-10 10:32:55 +01:00

168 lines
6.1 KiB
Swift

import UIKit
struct BatchArch {
var name: String
var data: Data
}
class MessageCollector: NSObject {
public static let shared = MessageCollector()
private var imagesWaiting = [BatchArch]()
private var imagesSending = [BatchArch]()
private var messagesWaiting = [Data]()
private var nextMessageIndex = 0
private var sendingLastMessages = false
private let maxMessagesSize = 500_000
private let messagesQueue = OperationQueue()
private let lateMessagesFile: URL?
private var sendInderval: Timer?
override init() {
lateMessagesFile = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first?.appendingPathComponent("/lateMessages.dat")
super.init()
}
func start() {
sendInderval = Timer.scheduledTimer(withTimeInterval: 5, repeats: true, block: { [weak self] _ in
self?.flush()
})
NotificationCenter.default.addObserver(self, selector: #selector(terminate), name: UIApplication.willResignActiveNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(terminate), name: UIApplication.willTerminateNotification, object: nil)
messagesQueue.maxConcurrentOperationCount = 1
if let fileUrl = lateMessagesFile,
FileManager.default.fileExists(atPath: fileUrl.path),
let lateData = try? Data(contentsOf: fileUrl) {
NetworkManager.shared.sendLateMessage(content: lateData) { (success) in
guard success else { return }
try? FileManager.default.removeItem(at: fileUrl)
}
}
}
func stop() {
sendInderval?.invalidate()
NotificationCenter.default.removeObserver(self, name: UIApplication.willResignActiveNotification, object: nil)
NotificationCenter.default.removeObserver(self, name: UIApplication.willTerminateNotification, object: nil)
self.terminate()
}
func sendImagesBatch(batch: Data, fileName: String) {
self.imagesWaiting.append(BatchArch(name: fileName, data: batch))
messagesQueue.addOperation {
self.flushImages()
}
}
@objc func terminate() {
guard !sendingLastMessages else { return }
messagesQueue.addOperation {
self.sendingLastMessages = true
self.flushMessages()
}
}
@objc func flush() {
messagesQueue.addOperation {
self.flushMessages()
self.flushImages()
}
}
private func flushImages() {
let images = imagesWaiting.first
guard !imagesWaiting.isEmpty, let images = images, let projectKey = ORTracker.shared.projectKey else { return }
imagesWaiting.remove(at: 0)
imagesSending.append(images)
DebugUtils.log("Sending images \(images.name) \(images.data.count)")
NetworkManager.shared.sendImages(projectKey: projectKey, images: images.data, name: images.name) { (success) in
self.imagesSending.removeAll { (waiting) -> Bool in
images.name == waiting.name
}
guard success else {
self.imagesWaiting.append(images)
return
}
}
}
func sendMessage(_ message: ORMessage) {
let data = message.contentData()
#if DEBUG
if !message.description.contains("IOSLog") && !message.description.contains("IOSNetworkCall") {
DebugUtils.log(message.description)
}
if let networkCallMessage = message as? ORIOSNetworkCall {
DebugUtils.log("-->> IOSNetworkCall(105): \(networkCallMessage.method) \(networkCallMessage.URL)")
}
#endif
self.sendRawMessage(data)
}
private var debounceTimer: Timer?
private var debouncedMessage: ORMessage?
func sendDebouncedMessage(_ message: ORMessage) {
debounceTimer?.invalidate()
debouncedMessage = message
debounceTimer = Timer.scheduledTimer(withTimeInterval: 2.0, repeats: false) { [weak self] _ in
if let debouncedMessage = self?.debouncedMessage {
self?.sendMessage(debouncedMessage)
self?.debouncedMessage = nil
}
}
}
func sendRawMessage(_ data: Data) {
messagesQueue.addOperation {
if data.count > self.maxMessagesSize {
DebugUtils.log("<><><>Single message size exceeded limit")
return
}
self.messagesWaiting.append(data)
var totalWaitingSize = 0
self.messagesWaiting.forEach { totalWaitingSize += $0.count }
if totalWaitingSize > Int(Double(self.maxMessagesSize) * 0.8) {
self.flushMessages()
}
}
}
private func flushMessages() {
var messages = [Data]()
var sentSize = 0
while let message = messagesWaiting.first, sentSize + message.count <= maxMessagesSize {
messages.append(message)
messagesWaiting.remove(at: 0)
sentSize += message.count
}
guard !messages.isEmpty else { return }
var content = Data()
let index = ORIOSBatchMeta(firstIndex: UInt64(nextMessageIndex))
content.append(index.contentData())
DebugUtils.log(index.description)
messages.forEach { (message) in
content.append(message)
}
if sendingLastMessages, let fileUrl = lateMessagesFile {
try? content.write(to: fileUrl)
}
nextMessageIndex += messages.count
DebugUtils.log("messages batch \(content)")
NetworkManager.shared.sendMessage(content: content) { (success) in
guard success else {
DebugUtils.log("<><>re-sending failed batch<><>")
self.messagesWaiting.insert(contentsOf: messages, at: 0)
return
}
if self.sendingLastMessages {
self.sendingLastMessages = false
if let fileUrl = self.lateMessagesFile, FileManager.default.fileExists(atPath: fileUrl.path) {
try? FileManager.default.removeItem(at: fileUrl)
}
}
}
}
}