Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid creating multiple clients if the NSE is invoked more than once … #2944

Merged
merged 2 commits into from
Jun 19, 2024
Merged
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
50 changes: 45 additions & 5 deletions NSE/Sources/NotificationServiceExtension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,11 @@ private let keychainController = KeychainController(service: .sessions,
class NotificationServiceExtension: UNNotificationServiceExtension {
private var handler: ((UNNotificationContent) -> Void)?
private var modifiedContent: UNMutableNotificationContent?


// Used to create one single UserSession across process/instances/runs
private static let serialQueue = DispatchQueue(label: "io.element.elementx.nse")
private static var userSession: NSEUserSession?

override func didReceive(_ request: UNNotificationRequest,
withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
guard !DataProtectionManager.isDeviceLockedAfterReboot(containerURL: URL.appGroupContainerDirectory),
Expand All @@ -73,6 +77,24 @@ class NotificationServiceExtension: UNNotificationServiceExtension {
MXLog.info("\(tag) #########################################")
NSELogger.logMemory(with: tag)
MXLog.info("\(tag) Payload came: \(request.content.userInfo)")

Self.serialQueue.sync {
if Self.userSession == nil {
// This function might be run concurrently and from different processes
// It's imperative that we create **at most** one UserSession/Client per process
Task.synchronous {
do {
Self.userSession = try await NSEUserSession(credentials: credentials, clientSessionDelegate: keychainController)
} catch {
MXLog.error("Failed creating user session with error: \(error)")
}
}
}

if Self.userSession == nil {
return discard(unreadCount: request.unreadCount)
}
}

Task {
await run(with: credentials,
Expand All @@ -94,12 +116,13 @@ class NotificationServiceExtension: UNNotificationServiceExtension {
eventId: String,
unreadCount: Int?) async {
MXLog.info("\(tag) run with roomId: \(roomId), eventId: \(eventId)")

guard let userSession = Self.userSession else {
MXLog.error("Invalid NSE User Session, discarding.")
return discard(unreadCount: unreadCount)
}

do {
// This function might be run concurrently and from different processes, let the SDK handle race conditions
// on fetching user sessions
let userSession = try await NSEUserSession(credentials: credentials, clientSessionDelegate: keychainController)

guard let itemProxy = await userSession.notificationItemProxy(roomID: roomId, eventID: eventId) else {
MXLog.info("\(tag) no notification for the event, discard")
return discard(unreadCount: unreadCount)
Expand Down Expand Up @@ -219,3 +242,20 @@ class NotificationServiceExtension: UNNotificationServiceExtension {
return false
}
}

// https://stackoverflow.com/a/77300959/730924
private extension Task where Failure == Error {
/// Performs an async task in a sync context.
///
/// - Note: This function blocks the thread until the given operation is finished. The caller is responsible for managing multithreading.
static func synchronous(priority: TaskPriority? = nil, operation: @escaping @Sendable () async throws -> Success) {
let semaphore = DispatchSemaphore(value: 0)

Task(priority: priority) {
defer { semaphore.signal() }
return try await operation()
}

semaphore.wait()
}
}
Loading