From 7478fb7f2955ef4475d50da108bb0168af1663f1 Mon Sep 17 00:00:00 2001 From: Max Cobb Date: Fri, 13 Jan 2023 14:08:40 +0000 Subject: [PATCH 1/3] handle flutter device swaps with RTM and incoming ints above Int32.max --- .../AgoraUIKit+AgoraRtmController+Extensions.swift | 2 +- .../AgoraUIKit+AgoraRtmController+MuteRequests.swift | 11 +++++++++-- Sources/Agora-Video-UIKit/AgoraUIKit.swift | 12 +++++++++--- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/Sources/Agora-Video-UIKit/AgoraUIKit+AgoraRtmController+Extensions.swift b/Sources/Agora-Video-UIKit/AgoraUIKit+AgoraRtmController+Extensions.swift index 2f896000..d4096cb6 100644 --- a/Sources/Agora-Video-UIKit/AgoraUIKit+AgoraRtmController+Extensions.swift +++ b/Sources/Agora-Video-UIKit/AgoraUIKit+AgoraRtmController+Extensions.swift @@ -109,7 +109,7 @@ extension AgoraVideoViewer: RtmControllerDelegate { func handleDecodedMessage(_ rtmAction: DecodedRtmAction, from peerId: String) { switch rtmAction { case .mute(let muteReq): - self.handleMuteRequest(muteReq: muteReq) + self.handleMuteRequest(muteReq: muteReq, from: peerId) case .userData(let user): AgoraVideoViewer.agoraPrint( .verbose, message: "Received user data: \n\(user.prettyPrint())" diff --git a/Sources/Agora-Video-UIKit/AgoraUIKit+AgoraRtmController+MuteRequests.swift b/Sources/Agora-Video-UIKit/AgoraUIKit+AgoraRtmController+MuteRequests.swift index b0cb23f2..21d09151 100644 --- a/Sources/Agora-Video-UIKit/AgoraUIKit+AgoraRtmController+MuteRequests.swift +++ b/Sources/Agora-Video-UIKit/AgoraUIKit+AgoraRtmController+MuteRequests.swift @@ -140,8 +140,15 @@ extension SingleVideoViewDelegate { extension AgoraVideoViewer { /// Handle mute request, by showing popup or directly changing the device state /// - Parameter muteReq: Incoming mute request data - public func handleMuteRequest(muteReq: MuteRequest) { - guard let device = MutingDevices(rawValue: muteReq.device) else { return } + public func handleMuteRequest(muteReq: MuteRequest, from peerId: String) { + guard var device = MutingDevices(rawValue: muteReq.device) else { return } + if let peerData = self.rtmLookup[peerId] as? AgoraVideoViewer.UserData, + peerData.uikit.framework == "flutter", + // Flutter 1.1.1 and below had camera and microphone swapped. + "1.1.1".compare(peerData.uikit.version) != .orderedAscending, + let newDevice = MutingDevices(rawValue: (muteReq.device + 1) % 2) { + device = newDevice + } if device == .camera, self.agoraSettings.cameraEnabled == !muteReq.mute { return } if device == .microphone, self.agoraSettings.micEnabled == !muteReq.mute { return } diff --git a/Sources/Agora-Video-UIKit/AgoraUIKit.swift b/Sources/Agora-Video-UIKit/AgoraUIKit.swift index 34d286de..c4d56434 100644 --- a/Sources/Agora-Video-UIKit/AgoraUIKit.swift +++ b/Sources/Agora-Video-UIKit/AgoraUIKit.swift @@ -13,7 +13,10 @@ import AgoraRtcKit public struct AgoraUIKit: Codable { /// Instance of the current AgoraUIKit instance. public static var current: AgoraUIKit { - AgoraUIKit(version: AgoraUIKit.version, platform: AgoraUIKit.platform, framework: AgoraUIKit.framework) + AgoraUIKit(version: AgoraUIKit.version, + platform: AgoraUIKit.platform, + framework: AgoraUIKit.framework + ) } /// Platform that is being used: ios, macos, android, unknown public fileprivate(set) var platform: String @@ -22,7 +25,7 @@ public struct AgoraUIKit: Codable { /// Framework type of UIKit. "native", "flutter", "reactnative" public fileprivate(set) var framework: String /// Version of UIKit being used - public static let version = "4.0.5" + public static let version = "4.0.6" /// Framework type of UIKit. "native", "flutter", "reactnative" public static let framework = "native" #if os(iOS) @@ -67,6 +70,9 @@ public struct AgoraUIKit: Codable { /// - Parameter userInt: Signed integer userId /// - Returns: Unsigned integer userId public static func intToUInt(_ userInt: Int) -> UInt { - UInt(UInt32(bitPattern: Int32(userInt))) + if userInt > Int(Int32.max), userInt <= Int(UInt32.max) { + return UInt(userInt) + } + return UInt(UInt32(bitPattern: Int32(userInt))) } } From 150f6be0e618a20ca0f77ee85fc8f9040b5582c1 Mon Sep 17 00:00:00 2001 From: Max Cobb Date: Fri, 13 Jan 2023 14:43:58 +0000 Subject: [PATCH 2/3] added a couple more test cases for RTM handling --- .../Agora-UIKit-Tests/RtmMessagingTests.swift | 99 ++++++------------- .../RtmUserIdHandlingTests.swift | 36 +++++++ 2 files changed, 65 insertions(+), 70 deletions(-) create mode 100644 Tests/Agora-UIKit-Tests/RtmUserIdHandlingTests.swift diff --git a/Tests/Agora-UIKit-Tests/RtmMessagingTests.swift b/Tests/Agora-UIKit-Tests/RtmMessagingTests.swift index 65680ed7..01529a50 100644 --- a/Tests/Agora-UIKit-Tests/RtmMessagingTests.swift +++ b/Tests/Agora-UIKit-Tests/RtmMessagingTests.swift @@ -1,39 +1,39 @@ import XCTest import AgoraRtcKit -#if canImport(AgoraRtmController) && canImport(AgoraUIKit_iOS) +#if canImport(AgoraRtmControl) && canImport(AgoraUIKit) import AgoraRtmKit -import AgoraRtmController -@testable import AgoraUIKit_iOS +import AgoraRtmControl +@testable import AgoraUIKit final class RtmMessagesTests: XCTestCase { func testEncodeMuteReq() throws { let muteReq = AgoraVideoViewer.MuteRequest( rtcId: 999, mute: true, device: .camera, isForceful: true ) - guard let rawMsg = AgoraRtmController.createRtmMessage(from: muteReq) else { + guard let rtmMessage = AgoraRtmController.createRtmMessage(from: muteReq) else { return XCTFail("MuteRequest should be encodable") } - XCTAssert(rawMsg.text == "AgoraUIKit", "Message text data should be AgoraUIKit") - let msgText = String(data: rawMsg.rawData, encoding: .utf8) - guard let unencodedJSON = try? JSONSerialization.jsonObject( - with: rawMsg.rawData, options: .allowFragments - ) as? [String: Any] else { + XCTAssert(rtmMessage.type == .text, "Message type should be .text") + guard let rawData = rtmMessage.text.data(using: .utf8), + let unencodedJSON = try! JSONSerialization.jsonObject( + with: rawData, options: []) as? [String: Any] + else { return XCTFail("Could not unencode data") } - XCTAssertEqual((unencodedJSON["rtcId"] as? UInt), muteReq.rtcId, "rtcId invalid!") + + guard let decodedMsg = AgoraVideoViewer.decodeRtmData( + data: rawData, from: "" + ) else { return XCTFail("Could not decode RTM data.") } + + XCTAssertEqual((unencodedJSON["rtcId"] as? Int), muteReq.rtcId, "rtcId invalid!") XCTAssertEqual((unencodedJSON["mute"] as? Bool), muteReq.mute, "mute invalid!") XCTAssertEqual((unencodedJSON["device"] as? Int), muteReq.device, "device invalid!") XCTAssertEqual((unencodedJSON["isForceful"] as? Bool), muteReq.isForceful, "mute invalid!") let msgTextValid = "{\"rtcId\":999,\"mute\":true,\"messageType\":" + "\"MuteRequest\",\"device\":0,\"isForceful\":true}" - XCTAssertEqual(msgText, msgTextValid, "Message text not matching mstTextValid") - guard let decodedMsg = AgoraVideoViewer.decodeRtmData( - data: rawMsg.rawData, from: "" - ) else { - return XCTFail("Failed to decode message") - } + XCTAssertEqual(rtmMessage.text, msgTextValid, "Message text not matching mstTextValid") switch decodedMsg { case .mute(let decodedMuteReq): @@ -53,63 +53,21 @@ final class RtmMessagesTests: XCTestCase { rtmId: "1234-5678", rtcId: 190, username: "username", role: AgoraClientRole.broadcaster.rawValue, agora: .current, uikit: .current ) - guard let rawMsg = AgoraRtmController.createRtmMessage(from: userData) else { + guard let rtmMessage = AgoraRtmController.createRtmMessage(from: userData) else { return XCTFail("UserData should be encodable") } - XCTAssert(rawMsg.text == "AgoraUIKit", "Message text data should be AgoraUIKit") - let msgText = String(data: rawMsg.rawData, encoding: .utf8) - guard let unencodedJSON = try? JSONSerialization.jsonObject( - with: rawMsg.rawData, options: .allowFragments - ) as? [String: Any] else { - return XCTFail("Could not unencode data") - } - XCTAssertEqual((unencodedJSON["rtcId"] as? UInt), userData.rtcId, "rtcId invalid!") - XCTAssertEqual((unencodedJSON["role"] as? Int), userData.role, "mute invalid!") - XCTAssertEqual((unencodedJSON["username"] as? String), userData.username, "device invalid!") - if let agoraData = unencodedJSON["agora"] as? [String: Any] { - XCTAssertEqual((agoraData["rtc"] as? String), AgoraRtcEngineKit.getSdkVersion(), "rtcId invalid!") - XCTAssertEqual((agoraData["rtm"] as? String), AgoraRtmKit.getSDKVersion(), "mute invalid!") - } else { XCTFail("Could not parse agora version data") } - let msgTextValid = "{\"uikit\":{" - + "\"platform\":\"ios\",\"version\":\"\(AgoraUIKit.version)\",\"framework\":\"native\"}," - + "\"role\":1,\"rtmId\":\"1234-5678\",\"username\":\"username\",\"agora\":{\"rtm\":" - + "\"\(AgoraRtmKit.getSDKVersion()!)\",\"rtc\":\"\(AgoraRtcEngineKit.getSdkVersion())\"}," - + "\"messageType\":\"UserData\",\"rtcId\":190}" + XCTAssert(rtmMessage.type == .text, "Message type should be .text") - XCTAssertEqual(msgText, msgTextValid, "Message text not matching msgTextValid") - guard let decodedMsg = AgoraVideoViewer.decodeRtmData(data: rawMsg.rawData, from: "") else { - return XCTFail("Failed to decode message") - } - - switch decodedMsg { - case .userData(let decodedUserData): - XCTAssertEqual(decodedUserData.rtcId, userData.rtcId, "rtcId invalid!") - XCTAssertEqual(decodedUserData.rtmId, userData.rtmId, "rtmId invalid!") - XCTAssertEqual(decodedUserData.agora.rtc, AgoraRtcEngineKit.getSdkVersion(), "RTC version invalid!") - XCTAssertEqual(decodedUserData.uikit.framework, "native", "RTC version invalid!") - default: - XCTFail("Should decode to userData") + guard let rawData = rtmMessage.text.data(using: .utf8), + let unencodedJSON = try! JSONSerialization.jsonObject( + with: rawData, options: []) as? [String: Any] + else { + return XCTFail("Could not unencode data") } - } - func testEncodeUserData() throws { - let userData = AgoraRtmController.UserData( - rtmId: "1234-5678", rtcId: 190, username: "username", - role: AgoraClientRole.broadcaster.rawValue, agora: .current, uikit: .current - ) - guard let rawMsg = AgoraRtmController.createRawRtm(from: userData) else { - return XCTFail("UserData should be encodable") - } - XCTAssert(rawMsg.text == "AgoraUIKit", "Message text data should be AgoraUIKit") - let msgText = String(data: rawMsg.rawData, encoding: .utf8) - guard let unencodedJSON = try? JSONSerialization.jsonObject( - with: rawMsg.rawData, options: .allowFragments - ) as? [String: Any] else { - return XCTFail("Could not unencode data") - } - XCTAssertEqual((unencodedJSON["rtcId"] as? UInt), userData.rtcId, "rtcId invalid!") + XCTAssertEqual((unencodedJSON["rtcId"] as? Int), userData.rtcId, "rtcId invalid!") XCTAssertEqual((unencodedJSON["role"] as? Int), userData.role, "mute invalid!") XCTAssertEqual((unencodedJSON["username"] as? String), userData.username, "device invalid!") if let agoraData = unencodedJSON["agora"] as? [String: Any] { @@ -122,10 +80,11 @@ final class RtmMessagesTests: XCTestCase { + "\"\(AgoraRtmKit.getSDKVersion()!)\",\"rtc\":\"\(AgoraRtcEngineKit.getSdkVersion())\"}," + "\"messageType\":\"UserData\",\"rtcId\":190}" - XCTAssertEqual(msgText, msgTextValid, "Message text not matching msgTextValid") - guard let decodedMsg = AgoraRtmController.decodeRawRtmData(data: rawMsg.rawData, from: "") else { - return XCTFail("Failed to decode message") - } + XCTAssertEqual(rtmMessage.text, msgTextValid, "Message text not matching msgTextValid") + + guard let decodedMsg = AgoraVideoViewer.decodeRtmData( + data: rawData, from: "" + ) else { return XCTFail("Could not decode RTM data.") } switch decodedMsg { case .userData(let decodedUserData): diff --git a/Tests/Agora-UIKit-Tests/RtmUserIdHandlingTests.swift b/Tests/Agora-UIKit-Tests/RtmUserIdHandlingTests.swift new file mode 100644 index 00000000..1cfc2916 --- /dev/null +++ b/Tests/Agora-UIKit-Tests/RtmUserIdHandlingTests.swift @@ -0,0 +1,36 @@ +// +// RtmUserIdHandlingTests.swift +// +// +// Created by Max Cobb on 13/01/2023. +// + +import XCTest +import AgoraRtcKit +@testable import AgoraUIKit + +final class RtmUserIdHandlingTests: XCTestCase { + + func testNegativeUID() throws { + let userData = AgoraVideoViewer.UserData( + rtmId: "example-test-rtm-id", rtcId: -512, + role: AgoraClientRole.broadcaster.rawValue + ) + XCTAssertEqual(userData.rtcId, -512, "UserData rtcId should be -512") + XCTAssertEqual( + userData.iOSUInt, 4294966784, + "UserData iOSUInt should be 4294966784" + ) + } + + func testLargeUID() throws { + let userData = AgoraVideoViewer.UserData( + rtmId: "example-test-rtm-id", rtcId: 4294966784, + role: AgoraClientRole.broadcaster.rawValue + ) + XCTAssertEqual( + userData.rtcId, Int(userData.iOSUInt ?? 0), + "UserData rtcId should be the same as iOSUInt" + ) + } +} From f326ff4f44fe62938e0f8ff08e6036e5893954ff Mon Sep 17 00:00:00 2001 From: Max Cobb Date: Fri, 13 Jan 2023 14:59:02 +0000 Subject: [PATCH 3/3] fix swiftlint errors. --- Tests/Agora-UIKit-Tests/RtmMessagingTests.swift | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Tests/Agora-UIKit-Tests/RtmMessagingTests.swift b/Tests/Agora-UIKit-Tests/RtmMessagingTests.swift index 01529a50..ea273bd6 100644 --- a/Tests/Agora-UIKit-Tests/RtmMessagingTests.swift +++ b/Tests/Agora-UIKit-Tests/RtmMessagingTests.swift @@ -16,7 +16,7 @@ final class RtmMessagesTests: XCTestCase { XCTAssert(rtmMessage.type == .text, "Message type should be .text") guard let rawData = rtmMessage.text.data(using: .utf8), - let unencodedJSON = try! JSONSerialization.jsonObject( + let unencodedJSON = try? JSONSerialization.jsonObject( with: rawData, options: []) as? [String: Any] else { return XCTFail("Could not unencode data") @@ -60,13 +60,12 @@ final class RtmMessagesTests: XCTestCase { XCTAssert(rtmMessage.type == .text, "Message type should be .text") guard let rawData = rtmMessage.text.data(using: .utf8), - let unencodedJSON = try! JSONSerialization.jsonObject( + let unencodedJSON = try? JSONSerialization.jsonObject( with: rawData, options: []) as? [String: Any] else { return XCTFail("Could not unencode data") } - XCTAssertEqual((unencodedJSON["rtcId"] as? Int), userData.rtcId, "rtcId invalid!") XCTAssertEqual((unencodedJSON["role"] as? Int), userData.role, "mute invalid!") XCTAssertEqual((unencodedJSON["username"] as? String), userData.username, "device invalid!")