Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
4a42e4d
progress
Velin92 Apr 12, 2024
9744e6d
qr code scanning now works, but the API is still unstable
Velin92 Apr 15, 2024
aee72b7
code improvements
Velin92 Apr 16, 2024
04aa596
code improvements
Velin92 Apr 16, 2024
086cb30
improved state handling
Velin92 Apr 18, 2024
223cf43
improved the flow
Velin92 Apr 22, 2024
529d943
the flow can now reach the login state
Velin92 Apr 22, 2024
1ff6cb4
now the code has also a string representation
Velin92 Apr 23, 2024
107f1fa
fix onboarding
Velin92 Apr 23, 2024
1060bf6
possible fix
Velin92 Apr 24, 2024
00345b2
qr code flow
Velin92 Apr 26, 2024
3741c62
possible fix
Velin92 Apr 26, 2024
9d2f179
fix for the connecting state not working properly
Velin92 Apr 29, 2024
638c298
Merge branch 'develop' of https://github.com/vector-im/element-x-ios …
Velin92 Apr 29, 2024
64b7934
Discard changes to ElementX.xcodeproj/project.xcworkspace/xcshareddat…
Velin92 Apr 29, 2024
45c04c5
updated copies
Velin92 Apr 29, 2024
af91e01
better code in the view model
Velin92 Apr 29, 2024
94dcff7
accountURL is now async
hughns Apr 30, 2024
5bd9dbb
Refix
hughns Apr 30, 2024
2beb697
Revert "Refix"
Velin92 Apr 30, 2024
da7af70
Revert "accountURL is now async"
Velin92 Apr 30, 2024
62bc4b8
Merge branch 'develop' of https://github.com/vector-im/element-x-ios …
Velin92 Apr 30, 2024
6854682
better error handling for now
Velin92 May 2, 2024
94b6798
qr code login sdk update for pr building
Velin92 May 2, 2024
6ea8cde
Merge branch 'develop' into mauroromito/qr_code_api_testing
Velin92 May 2, 2024
4dd54d8
updated tests
Velin92 May 2, 2024
525979b
Merge branch 'mauroromito/qr_code_api_testing' of https://github.com/…
Velin92 May 2, 2024
63efacb
added some view model tests
Velin92 May 2, 2024
5e652ab
test improvement
Velin92 May 2, 2024
6adbc02
Merge branch 'develop' of https://github.com/vector-im/element-x-ios …
Velin92 May 7, 2024
85acde7
updated the branch
Velin92 May 7, 2024
8bdd4f7
Merge branch 'develop' into mauroromito/qr_code_api_testing
Velin92 May 7, 2024
d5b5604
updated strings
Velin92 May 9, 2024
44acd0a
Merge branch 'develop' of https://github.com/vector-im/element-x-ios …
Velin92 May 17, 2024
16fe49f
Merge branch 'develop' of https://github.com/vector-im/element-x-ios …
Velin92 May 20, 2024
b44498e
error handling
Velin92 May 20, 2024
dd9447d
fixing tests
Velin92 May 20, 2024
6b4d84e
code improvement
Velin92 May 21, 2024
6b56f17
updated qr code login
Velin92 May 21, 2024
a3ac12a
Merge branch 'develop' of https://github.com/vector-im/element-x-ios …
Velin92 May 23, 2024
a60b19a
updated previews and added an error for the
Velin92 May 23, 2024
26dfc77
Merge branch 'develop' of https://github.com/vector-im/element-x-ios …
Velin92 May 29, 2024
1e7b74b
updated sdk
Velin92 May 29, 2024
ce9dc5e
fixed tests
Velin92 May 29, 2024
b70c072
qr code login enabled is by default false
Velin92 May 29, 2024
8c49170
added a new error case
Velin92 May 29, 2024
f7c3bd1
Apply suggestions from code review
Velin92 May 29, 2024
6af30f3
some pr comments have been addressed
Velin92 May 29, 2024
58587e8
updated proj
Velin92 May 29, 2024
e21df9c
moved request camera in app mediator
Velin92 May 29, 2024
a8b870b
added a test for qr code decoding
Velin92 May 29, 2024
10c13e0
enable the feature for development builds by default
Velin92 May 29, 2024
0c099dc
Merge branch 'develop' into mauroromito/qr_code_api_testing
Velin92 May 29, 2024
0129a1b
qr code login always enabled in nightly
Velin92 May 29, 2024
848c6ee
removed from developer settings
Velin92 May 29, 2024
683a534
Merge branch 'mauroromito/qr_code_api_testing' of https://github.com/…
Velin92 May 29, 2024
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
8 changes: 7 additions & 1 deletion ElementX/Sources/Application/AppCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -414,10 +414,16 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
}

private func startAuthentication() {
let encryptionKeyProvider = EncryptionKeyProvider()
let authenticationService = AuthenticationServiceProxy(userSessionStore: userSessionStore,
encryptionKeyProvider: EncryptionKeyProvider(),
encryptionKeyProvider: encryptionKeyProvider,
appSettings: appSettings)
let qrCodeLoginService = QRCodeLoginService(oidcConfiguration: appSettings.oidcConfiguration.rustValue,
encryptionKeyProvider: encryptionKeyProvider,
userSessionStore: userSessionStore)

authenticationFlowCoordinator = AuthenticationFlowCoordinator(authenticationService: authenticationService,
qrCodeLoginService: qrCodeLoginService,
bugReportService: ServiceLocator.shared.bugReportService,
navigationRootCoordinator: navigationRootCoordinator,
appMediator: appMediator,
Expand Down
9 changes: 9 additions & 0 deletions ElementX/Sources/Application/AppSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,15 @@ final class AppSettings {
return url
}()

private(set) lazy var oidcConfiguration = OIDCConfigurationProxy(clientName: InfoPlistReader.main.bundleDisplayName,
redirectURI: oidcRedirectURL,
clientURI: websiteURL,
logoURI: logoURL,
tosURI: acceptableUseURL,
policyURI: privacyURL,
contacts: [supportEmailAddress],
staticRegistrations: oidcStaticRegistrations.mapKeys { $0.absoluteString })

/// A dictionary of accounts that have performed an initial sync through their proxy.
///
/// This is a temporary workaround. In the future we should be able to receive a signal from the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
private let appSettings: AppSettings
private let analytics: AnalyticsService
private let userIndicatorController: UserIndicatorControllerProtocol
private let qrCodeLoginService: QRCodeLoginServiceProtocol

private var cancellables = Set<AnyCancellable>()

Expand All @@ -42,6 +43,7 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
weak var delegate: AuthenticationFlowCoordinatorDelegate?

init(authenticationService: AuthenticationServiceProxyProtocol,
qrCodeLoginService: QRCodeLoginServiceProtocol,
bugReportService: BugReportServiceProtocol,
navigationRootCoordinator: NavigationRootCoordinator,
appMediator: AppMediatorProtocol,
Expand All @@ -55,6 +57,7 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
self.appSettings = appSettings
self.analytics = analytics
self.userIndicatorController = userIndicatorController
self.qrCodeLoginService = qrCodeLoginService

navigationStackCoordinator = NavigationStackCoordinator()
}
Expand Down Expand Up @@ -106,7 +109,7 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
}

private func startQRCodeLogin() {
let coordinator = QRCodeLoginScreenCoordinator(parameters: .init(qrCodeLoginService: QRCodeLoginService(),
let coordinator = QRCodeLoginScreenCoordinator(parameters: .init(qrCodeLoginService: qrCodeLoginService,
orientationManager: appMediator.windowManager,
appMediator: appMediator))
coordinator.actionsPublisher.sink { [weak self] action in
Expand All @@ -116,6 +119,13 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
switch action {
case .cancel:
navigationStackCoordinator.setSheetCoordinator(nil)
case .done(let userSession):
navigationStackCoordinator.setSheetCoordinator(nil)
// Since the qr code login flow includes verification
appSettings.hasRunIdentityConfirmationOnboarding = true
DispatchQueue.main.async {
self.userHasSignedIn(userSession: userSession)
}
}
}
.store(in: &cancellables)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,9 @@ class OnboardingFlowCoordinator: FlowCoordinatorProtocol {

configureStateMachine()

stateMachine.tryEvent(.next)

rootNavigationStackCoordinator.setFullScreenCoverCoordinator(navigationStackCoordinator, animated: !isNewLogin)

stateMachine.tryEvent(.next)
}

func handleAppRoute(_ appRoute: AppRoute, animated: Bool) {
Expand Down Expand Up @@ -134,6 +134,8 @@ class OnboardingFlowCoordinator: FlowCoordinatorProtocol {
return .analyticsPrompt
case (.initial, false, false, false, true):
return .notificationPermissions
case (.initial, false, false, false, false):
return .finished

case (.identityConfirmation, _, _, _, _):
return .identityConfirmed
Expand Down
73 changes: 73 additions & 0 deletions ElementX/Sources/Mocks/Generated/GeneratedMocks.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6725,6 +6725,11 @@ class PollInteractionHandlerMock: PollInteractionHandlerProtocol {
}
}
class QRCodeLoginServiceMock: QRCodeLoginServiceProtocol {
var qrLoginProgressPublisher: AnyPublisher<QrLoginProgress, Never> {
get { return underlyingQrLoginProgressPublisher }
set(value) { underlyingQrLoginProgressPublisher = value }
}
var underlyingQrLoginProgressPublisher: AnyPublisher<QrLoginProgress, Never>!

//MARK: - requestAuthorizationIfNeeded

Expand Down Expand Up @@ -6790,6 +6795,74 @@ class QRCodeLoginServiceMock: QRCodeLoginServiceProtocol {
return requestAuthorizationIfNeededReturnValue
}
}
//MARK: - loginWithQRCode

var loginWithQRCodeDataUnderlyingCallsCount = 0
var loginWithQRCodeDataCallsCount: Int {
get {
if Thread.isMainThread {
return loginWithQRCodeDataUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = loginWithQRCodeDataUnderlyingCallsCount
}

return returnValue!
}
}
set {
if Thread.isMainThread {
loginWithQRCodeDataUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
loginWithQRCodeDataUnderlyingCallsCount = newValue
}
}
}
}
var loginWithQRCodeDataCalled: Bool {
return loginWithQRCodeDataCallsCount > 0
}
var loginWithQRCodeDataReceivedData: Data?
var loginWithQRCodeDataReceivedInvocations: [Data] = []

var loginWithQRCodeDataUnderlyingReturnValue: Result<UserSessionProtocol, QRCodeLoginServiceError>!
var loginWithQRCodeDataReturnValue: Result<UserSessionProtocol, QRCodeLoginServiceError>! {
get {
if Thread.isMainThread {
return loginWithQRCodeDataUnderlyingReturnValue
} else {
var returnValue: Result<UserSessionProtocol, QRCodeLoginServiceError>? = nil
DispatchQueue.main.sync {
returnValue = loginWithQRCodeDataUnderlyingReturnValue
}

return returnValue!
}
}
set {
if Thread.isMainThread {
loginWithQRCodeDataUnderlyingReturnValue = newValue
} else {
DispatchQueue.main.sync {
loginWithQRCodeDataUnderlyingReturnValue = newValue
}
}
}
}
var loginWithQRCodeDataClosure: ((Data) async -> Result<UserSessionProtocol, QRCodeLoginServiceError>)?

func loginWithQRCode(data: Data) async -> Result<UserSessionProtocol, QRCodeLoginServiceError> {
loginWithQRCodeDataCallsCount += 1
loginWithQRCodeDataReceivedData = data
loginWithQRCodeDataReceivedInvocations.append(data)
if let loginWithQRCodeDataClosure = loginWithQRCodeDataClosure {
return await loginWithQRCodeDataClosure(data)
} else {
return loginWithQRCodeDataReturnValue
}
}
}
class RoomDirectorySearchProxyMock: RoomDirectorySearchProxyProtocol {
var resultsPublisher: CurrentValuePublisher<[RoomDirectorySearchResult], Never> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
//
// Copyright 2024 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

// Helpers to remove ECI headers from QR Code raw data
// https://gist.github.com/PetrusM/267e2ee8c1d8b5dca17eac085afa7d7c
import AVKit
import Foundation

extension AVMetadataMachineReadableCodeObject {
var binaryValue: Data? {
switch type {
case .qr:
guard let binaryValueWithProtocol else {
return nil
}
return removeQrProtocolData(binaryValueWithProtocol)
case .aztec:
guard let string = stringValue
else { return nil }
return string.data(using: String.Encoding.isoLatin1)
default:
return nil
}
}

var binaryValueWithProtocol: Data? {
guard let descriptor else {
return nil
}
switch type {
case .qr:
return (descriptor as? CIQRCodeDescriptor)?.errorCorrectedPayload
case .aztec:
return (descriptor as? CIAztecCodeDescriptor)?.errorCorrectedPayload
case .pdf417:
return (descriptor as? CIPDF417CodeDescriptor)?.errorCorrectedPayload
case .dataMatrix:
return (descriptor as? CIDataMatrixCodeDescriptor)?.errorCorrectedPayload
default:
return nil
}
}

private func removeQrProtocolData(_ input: Data) -> Data? {
var halves = input.halfBytes()
var batch = takeBatch(&halves)
var output = batch
while !batch.isEmpty {
batch = takeBatch(&halves)
output.append(contentsOf: batch)
}
return Data(output)
}

private func takeBatch(_ input: inout [HalfByte]) -> [UInt8] {
guard let version = (descriptor as? CIQRCodeDescriptor)?.symbolVersion else {
return []
}
let characterCountLength = version > 9 ? 16 : 8
let mode = input.remove(at: 0)
var output = [UInt8]()
switch mode.value {
// If there is not only binary in the QRCode, then cases should be added here.
case 0x04: // Binary
let charactersCount: UInt16
if characterCountLength == 8 {
charactersCount = UInt16(input.takeUInt8())
} else {
charactersCount = UInt16(input.takeUInt16())
}
for _ in 0..<charactersCount {
output.append(input.takeUInt8())
}
return output
case 0x00: // End of data
return []
default:
return []
}
}
}

private struct HalfByte {
let value: UInt8
}

private extension [HalfByte] {
mutating func takeUInt8() -> UInt8 {
let left = remove(at: 0)
let right = remove(at: 0)
return UInt8(left, right)
}

mutating func takeUInt16() -> UInt16 {
let first = remove(at: 0)
let second = remove(at: 0)
let third = remove(at: 0)
let fourth = remove(at: 0)
return UInt16(first, second, third, fourth)
}
}

private extension Data {
func halfBytes() -> [HalfByte] {
var result = [HalfByte]()
forEach { (byte: UInt8) in
result.append(contentsOf: byte.halfBytes())
}
return result
}

init(_ halves: [HalfByte]) {
var halves = halves
var result = [UInt8]()
while halves.count > 1 {
result.append(halves.takeUInt8())
}
self.init(result)
}
}

private extension UInt8 {
func halfBytes() -> [HalfByte] {
[HalfByte(value: self >> 4), HalfByte(value: self & 0x0F)]
}

init(_ left: HalfByte, _ right: HalfByte) {
self.init((left.value << 4) + (right.value & 0x0F))
}
}

private extension UInt16 {
init(_ first: HalfByte, _ second: HalfByte, _ third: HalfByte, _ fourth: HalfByte) {
let first = UInt16(first.value) << 12
let second = UInt16(second.value) << 8
let third = UInt16(third.value) << 4
let fourth = UInt16(fourth.value) & 0x0F
let result = first + second + third + fourth
self.init(result)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ struct QRCodeLoginScreenCoordinatorParameters {

enum QRCodeLoginScreenCoordinatorAction {
case cancel
case done(userSession: UserSessionProtocol)
}

final class QRCodeLoginScreenCoordinator: CoordinatorProtocol {
Expand Down Expand Up @@ -54,6 +55,8 @@ final class QRCodeLoginScreenCoordinator: CoordinatorProtocol {
switch action {
case .cancel:
self.actionsSubject.send(.cancel)
case .done(let userSession):
self.actionsSubject.send(.done(userSession: userSession))
}
}
.store(in: &cancellables)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import Foundation

enum QRCodeLoginScreenViewModelAction {
case cancel
case done(userSession: UserSessionProtocol)
}

struct QRCodeLoginScreenViewState: BindableState {
Expand Down Expand Up @@ -53,6 +54,12 @@ struct QRCodeLoginScreenViewState: BindableState {
AttributedString(L10n.screenQrCodeLoginConnectionNoteSecureStateListItem2),
AttributedString(L10n.screenQrCodeLoginConnectionNoteSecureStateListItem3)
]

var bindings = QRCodeLoginScreenViewStateBindings()
}

struct QRCodeLoginScreenViewStateBindings {
var qrResult: Data?
}

enum QRCodeLoginScreenViewAction {
Expand Down Expand Up @@ -108,4 +115,13 @@ enum QRCodeLoginState: Equatable {
return false
}
}

var isScanning: Bool {
switch self {
case .scan(let state):
return state == .scanning
default:
return false
}
}
}
Loading