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
4 changes: 2 additions & 2 deletions WireAVS/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ let package = Package(
targets: [
.binaryTarget(
name: "WireAVS",
url: "https://github.com/wireapp/wire-avs/releases/download/10.1.57/avs.xcframework.zip",
checksum: "2231a0582a2f7217c2a7a3d9884dbe314e1dcc04ea3ee78c3e78937f9b78b039"
url: "https://github.com/wireapp/wire-avs/releases/download/10.1.62/avs.xcframework.zip",
checksum: "6dc362a9de57ebba0cf9fb9b67bfeb3ba469d5da7e4196ccf448b39d1050b0d2"
)
]
)
6 changes: 4 additions & 2 deletions WireDomain/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,17 @@ let package = Package(
.package(path: "../WireNetwork"),
.package(path: "../WireFoundation"),
.package(path: "../WireLogging"),
.package(path: "../WirePlugins")
.package(path: "../WirePlugins"),
.package(path: "../WireAVS")
],
targets: [
.target(
name: "WireDomainPackage",
dependencies: [
"WireNetwork",
"WireLogging",
"WireFoundation"
"WireFoundation",
"WireAVS"
]
),
.target(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,17 +59,21 @@ struct MLSMessageDecryptor: MLSMessageDecryptorProtocol {
id: conversationID.id,
domain: conversationID.domain
) else {
WireLogger.mls.error("WOW NSE MLS: conversation not found for \(conversationID.id)")
throw MLSMessageDecryptorError.conversationNotFound
}

guard let (mlsGroupID, isMLSReady) = await conversationLocalStore.mlsConversationInfo(
conversation: mlsConversation
) else {
// MLS conversation should have a group id.
WireLogger.mls.error("WOW NSE MLS: missing MLS group ID for conversation \(conversationID.id)")
throw MLSMessageDecryptorError.missingMLSGroupID
}
WireLogger.mls.info("WOW NSE MLS: decrypting for groupID=\(mlsGroupID), isMLSReady=\(isMLSReady), context=\(context != nil ? "present" : "nil")")

guard isMLSReady else {
WireLogger.mls.error("WOW NSE MLS: MLS conversation not ready for \(conversationID.id)")
throw MLSMessageDecryptorError.mlsConversationNotReady
}

Expand All @@ -94,6 +98,7 @@ struct MLSMessageDecryptor: MLSMessageDecryptorProtocol {

var decryptedEvent = eventData
decryptedEvent.decryptedMessages = decryptedMessages
WireLogger.mls.info("WOW NSE MLS: decryptionResults count=\(decryptionResults.count) for groupID=\(mlsGroupID)")

return decryptedEvent
} catch let error as WireDataModel.MLSDecryptionService.MLSMessageDecryptionError {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,13 @@ struct ConversationCallingEventNotificationBuilder: ConversationCallingEventNoti

if displayCallKitNotification {
// First, let's try to return a CallKit notification if possible.
return await buildCallKitNotification(
callContent: callContent,
accountID: accountID,
conversationID: resolvedConversationID,
senderID: senderID
)
// return await buildCallKitNotification(
// callContent: callContent,
// accountID: accountID,
// conversationID: resolvedConversationID,
// senderID: senderID
// )
return nil

} else if displayCallNotification {
// If not, try to return a regular call notification.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ struct ConversationMessageAddEventNotificationBuilder: ConversationMessageAddEve
conversationID: content.conversationID,
senderID: content.senderID
) {
callingNotification
callingNotification //nil
} else {
await buildMessageContentNotification(
message: content.message,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
//
// Wire
// Copyright (C) 2026 Wire Swiss GmbH
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses/.
//

import Foundation
import avs
import WireLogging

public enum CallClosedReason: Int32 {
case normal, canceled, answeredElsewhere, rejectedElsewhere
case timeout, lostMedia, internalError, inputOutputError
case stillOngoing, securityDegraded, outdatedClient
case datachannel, timeoutECONN, noOneJoined, everyoneLeft, unknown

init(wcall_reason: Int32) {

Check warning on line 29 in WireDomain/Sources/WireDomain/Notifications/Components/AVSCallingEventService.swift

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename this parameter to match the regular expression ^[a-z][a-zA-Z0-9]*$.

See more on https://sonarcloud.io/project/issues?id=wireapp_wire-ios&issues=AZy4P80EdyS88GpbDBVN&open=AZy4P80EdyS88GpbDBVN&pullRequest=4389
switch wcall_reason {
case WCALL_REASON_NORMAL: self = .normal
case WCALL_REASON_CANCELED: self = .canceled
case WCALL_REASON_ANSWERED_ELSEWHERE: self = .answeredElsewhere
case WCALL_REASON_REJECTED: self = .rejectedElsewhere
case WCALL_REASON_TIMEOUT: self = .timeout
case WCALL_REASON_LOST_MEDIA: self = .lostMedia
case WCALL_REASON_ERROR: self = .internalError
case WCALL_REASON_IO_ERROR: self = .inputOutputError
case WCALL_REASON_STILL_ONGOING: self = .stillOngoing
case WCALL_REASON_OUTDATED_CLIENT: self = .outdatedClient
case WCALL_REASON_TIMEOUT_ECONN: self = .timeoutECONN
case WCALL_REASON_DATACHANNEL: self = .datachannel
case WCALL_REASON_NOONE_JOINED: self = .noOneJoined
case WCALL_REASON_EVERYONE_LEFT: self = .everyoneLeft
default: self = .unknown
}
}
}

public protocol AVSCallingEventServiceProtocol: AnyObject {

var onIncomingCall: ((_ conversationId: String, _ shouldRing: Bool, _ isVideoCall: Bool) -> Void)? { get set }
var onMissedCall: ((_ conversationId: String, _ messageTime: Date, _ isVideoCall: Bool) -> Void)? { get set }
var onCallClosed: ((_ reason: CallClosedReason, _ conversationId: String) -> Void)? { get set }

func start()
func process(data: Data,
currentTime: UInt32,
serverTime: UInt32,
conversationId: String,
userId: String,
clientId: String,
conversationType: Int32
)
func end()
}

public final class AVSCallingEventService: AVSCallingEventServiceProtocol {

// MARK: - Closure properties (set by the caller in NSEClientScope)

public var onIncomingCall: ((_ conversationId: String, _ shouldRing: Bool, _ isVideoCall: Bool) -> Void)?
public var onMissedCall: ((_ conversationId: String, _ messageTime: Date, _ isVideoCall: Bool) -> Void)?
public var onCallClosed: ((_ reason: CallClosedReason, _ conversationId: String) -> Void)?

// MARK: - Private

private var handle: UInt32
private var contextPointer: UnsafeMutableRawPointer?

// MARK: - Init
private static let avsInitialize: Void = {
let result = wcall_init(WCALL_ENV_DEFAULT)
if result != 0 {
WireLogger.calling.error("NSE AVS: wcall_init failed with code \(result)", attributes: .newNSE, .safePublic)
}

}()

public init(userID: String, clientID: String) {
//Self.avsInitialize
self.handle = 0
wcall_set_log_handler({ level, msgPtr, _ in
guard let msg = msgPtr.flatMap({ String(cString: $0) }) else { return }
WireLogger.calling.debug(msg, attributes: .newNSE, .safePublic)
}, nil)
let retained = Unmanaged.passRetained(self)
self.contextPointer = retained.toOpaque()
self.handle = wcall_event_create(
userID,
clientID,
Self.incomingCallHandler,
Self.missedCallHandler,
Self.closedCallHandler,
contextPointer
)
WireLogger.calling.info("WOW NSE AVS: wcall_event_create handle=\(self.handle)", attributes: .newNSE, .safePublic)
}
deinit {
if let ptr = contextPointer {
Unmanaged<AVSCallingEventService>.fromOpaque(ptr).release()
}
WireLogger.calling.info("12345 caled DEINIT", attributes: .newNSE, .safePublic)

}

// MARK: - AVSCallingEventServiceProtocol

public func start() {
wcall_event_start(handle)
}

public func process(
data: Data,
currentTime: UInt32,
serverTime: UInt32,
conversationId: String,
userId: String,
clientId: String,
conversationType: Int32
) {
data.withUnsafeBytes { (ptr: UnsafeRawBufferPointer) in
guard let bytes = ptr.baseAddress?.assumingMemoryBound(to: UInt8.self) else { return }
wcall_event_process(
handle,
bytes,
data.count,
currentTime,
serverTime,
conversationId,
userId,
clientId,
conversationType
)
}
}

public func end() {
wcall_event_end(handle)
}

// MARK: - Static C Callbacks
//
// These match the exact C function pointer signatures expected by wcall_create.
// contextRef is the Unmanaged pointer to self, set during init.

private static let incomingCallHandler: @convention(c) (
UnsafePointer<Int8>?, // conversationId
UInt32, // messageTime
UnsafePointer<Int8>?, // userId
UnsafePointer<Int8>?, // clientId
Int32, // isVideoCall (1 = true)
Int32, // shouldRing (1 = true)
Int32, // conversationType
UnsafeMutableRawPointer? // contextRef → self
) -> Void = { conversationIdPtr, _, _, _, isVideoCallFlag, shouldRingFlag, _, contextRef in
guard
let contextRef,
let conversationId = conversationIdPtr.flatMap({ String(cString: $0) })
else { return }

let service = Unmanaged<AVSCallingEventService>.fromOpaque(contextRef).takeUnretainedValue()
WireLogger.mls.info("12345 \(service.onIncomingCall == nil), conversationId = \(conversationId)")
service.onIncomingCall?(
conversationId,
shouldRingFlag == 1,
isVideoCallFlag == 1
)
}

private static let missedCallHandler: @convention(c) (
UnsafePointer<Int8>?, // conversationId
UInt32, // messageTime
UnsafePointer<Int8>?, // userId
UnsafePointer<Int8>?, // clientId
Int32, // isVideoCall (1 = true)
UnsafeMutableRawPointer? // contextRef → self
) -> Void = { conversationIdPtr, messageTime, _, _, isVideoCallFlag, contextRef in
guard
let contextRef,
let conversationId = conversationIdPtr.flatMap({ String(cString: $0) })
else { return }

let service = Unmanaged<AVSCallingEventService>.fromOpaque(contextRef).takeUnretainedValue()
// Mirror AVSWrapper: treat messageTime=0 as "now"
let nonZeroTime = messageTime != 0 ? messageTime : UInt32(Date().timeIntervalSince1970)
service.onMissedCall?(
conversationId,
Date(timeIntervalSince1970: TimeInterval(nonZeroTime)),
isVideoCallFlag == 1
)
}

private static let closedCallHandler: @convention(c) (
Int32, // reason (WCALL_REASON_*)
UnsafePointer<Int8>?, // conversationId
UInt32, // messageTime
UnsafePointer<Int8>?, // userId
UnsafePointer<Int8>?, // clientId
UnsafeMutableRawPointer? // contextRef → self
) -> Void = { reasonCode, conversationIdPtr, _, _, _, contextRef in
guard
let contextRef,
let conversationId = conversationIdPtr.flatMap({ String(cString: $0) })
else { return }

let service = Unmanaged<AVSCallingEventService>.fromOpaque(contextRef).takeUnretainedValue()
service.onCallClosed?(
CallClosedReason(wcall_reason: reasonCode),
conversationId
)
}
}
Loading