mirror of
https://github.com/vector-im/element-ios.git
synced 2024-09-28 23:32:41 +00:00
Send the Signup analytics event (#6118)
* Implement the Signup event from AuthenticationViewController. Use AuthenticationFlow instead of MXKAuthenticationType. Create new AuthenticationType that aligns with AnalyticsEvent naming. Add additional cases from AnalyticsEvents.
This commit is contained in:
parent
f81bd5d656
commit
54a4feb0ea
21 changed files with 232 additions and 54 deletions
|
@ -183,7 +183,7 @@ EXTERNAL SOURCES:
|
||||||
|
|
||||||
CHECKOUT OPTIONS:
|
CHECKOUT OPTIONS:
|
||||||
AnalyticsEvents:
|
AnalyticsEvents:
|
||||||
:commit: f37a2f243270bffdcddfa5cbaeb4379e0db581c2
|
:commit: b275ccb194a219a61b3100159d51cadbf7c9020c
|
||||||
:git: https://github.com/matrix-org/matrix-analytics-events.git
|
:git: https://github.com/matrix-org/matrix-analytics-events.git
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
|
|
|
@ -226,6 +226,13 @@ extension Analytics {
|
||||||
client.updateUserProperties(userProperties)
|
client.updateUserProperties(userProperties)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Track the registration of a new user.
|
||||||
|
/// - Parameter authenticationType: The type of authentication that was used.
|
||||||
|
func trackSignup(authenticationType: AnalyticsEvent.Signup.AuthenticationType) {
|
||||||
|
let event = AnalyticsEvent.Signup(authenticationType: authenticationType)
|
||||||
|
capture(event: event)
|
||||||
|
}
|
||||||
|
|
||||||
/// Track the presentation of a screen
|
/// Track the presentation of a screen
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - screen: The screen that was shown.
|
/// - screen: The screen that was shown.
|
||||||
|
|
|
@ -26,6 +26,7 @@ import AnalyticsEvents
|
||||||
case slashCommand
|
case slashCommand
|
||||||
case spaceHierarchy
|
case spaceHierarchy
|
||||||
case timeline
|
case timeline
|
||||||
|
case permalink
|
||||||
|
|
||||||
var trigger: AnalyticsEvent.JoinedRoom.Trigger? {
|
var trigger: AnalyticsEvent.JoinedRoom.Trigger? {
|
||||||
switch self {
|
switch self {
|
||||||
|
@ -45,6 +46,8 @@ import AnalyticsEvents
|
||||||
return .SpaceHierarchy
|
return .SpaceHierarchy
|
||||||
case .timeline:
|
case .timeline:
|
||||||
return .Timeline
|
return .Timeline
|
||||||
|
case .permalink:
|
||||||
|
return .MobilePermalink
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,11 @@ import AnalyticsEvents
|
||||||
case inCall
|
case inCall
|
||||||
case spaceMenu
|
case spaceMenu
|
||||||
case spaceSettings
|
case spaceSettings
|
||||||
|
case roomPreview
|
||||||
|
case permalink
|
||||||
|
case linkShare
|
||||||
|
case exploreRooms
|
||||||
|
case spaceMembers
|
||||||
|
|
||||||
var trigger: AnalyticsEvent.ViewRoom.Trigger? {
|
var trigger: AnalyticsEvent.ViewRoom.Trigger? {
|
||||||
switch self {
|
switch self {
|
||||||
|
@ -84,6 +89,16 @@ import AnalyticsEvents
|
||||||
return .MobileSpaceMenu
|
return .MobileSpaceMenu
|
||||||
case .spaceSettings:
|
case .spaceSettings:
|
||||||
return .MobileSpaceSettings
|
return .MobileSpaceSettings
|
||||||
|
case .roomPreview:
|
||||||
|
return .MobileRoomPreview
|
||||||
|
case .permalink:
|
||||||
|
return .MobilePermalink
|
||||||
|
case .linkShare:
|
||||||
|
return .MobileLinkShare
|
||||||
|
case .exploreRooms:
|
||||||
|
return .MobileExploreRooms
|
||||||
|
case .spaceMembers:
|
||||||
|
return .MobileSpaceMembers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ import Foundation
|
||||||
enum AuthenticationCoordinatorResult {
|
enum AuthenticationCoordinatorResult {
|
||||||
/// The user has authenticated but key verification is yet to happen. The session value is
|
/// The user has authenticated but key verification is yet to happen. The session value is
|
||||||
/// for a fresh session that still needs to load, sync etc before being ready.
|
/// for a fresh session that still needs to load, sync etc before being ready.
|
||||||
case didLogin(session: MXSession, authenticationType: MXKAuthenticationType)
|
case didLogin(session: MXSession, authenticationFlow: AuthenticationFlow, authenticationType: AuthenticationType)
|
||||||
/// All of the required authentication steps including key verification is complete.
|
/// All of the required authentication steps including key verification is complete.
|
||||||
case didComplete
|
case didComplete
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@ protocol AuthenticationCoordinatorProtocol: Coordinator, Presentable {
|
||||||
var customServerFieldsVisible: Bool { get set }
|
var customServerFieldsVisible: Bool { get set }
|
||||||
|
|
||||||
/// Update the screen to display registration or login.
|
/// Update the screen to display registration or login.
|
||||||
func update(authenticationType: MXKAuthenticationType)
|
func update(authenticationFlow: AuthenticationFlow)
|
||||||
|
|
||||||
/// Force a registration process based on a predefined set of parameters from a server provisioning link.
|
/// Force a registration process based on a predefined set of parameters from a server provisioning link.
|
||||||
/// For more information see `AuthenticationViewController.externalRegistrationParameters`.
|
/// For more information see `AuthenticationViewController.externalRegistrationParameters`.
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
#import "MatrixKit.h"
|
#import "MatrixKit.h"
|
||||||
|
|
||||||
@protocol AuthenticationViewControllerDelegate;
|
@protocol AuthenticationViewControllerDelegate;
|
||||||
|
@class SSOIdentityProvider;
|
||||||
|
|
||||||
|
|
||||||
@interface AuthenticationViewController : MXKAuthenticationViewController <MXKAuthenticationViewControllerDelegate>
|
@interface AuthenticationViewController : MXKAuthenticationViewController <MXKAuthenticationViewControllerDelegate>
|
||||||
|
@ -60,8 +61,16 @@
|
||||||
|
|
||||||
@protocol AuthenticationViewControllerDelegate <NSObject>
|
@protocol AuthenticationViewControllerDelegate <NSObject>
|
||||||
|
|
||||||
- (void)authenticationViewController:(AuthenticationViewController *)authenticationViewController
|
/**
|
||||||
|
Notifies the delegate that authentication has succeeded.
|
||||||
|
@param authenticationViewController The view controller that handled the authentication.
|
||||||
|
@param session The session for the authenticated account.
|
||||||
|
@param password Optional password used for authentication (to be handed to the verification flow).
|
||||||
|
@param identityProvider Optional SSO identity provider used for authentication.
|
||||||
|
*/
|
||||||
|
- (void)authenticationViewController:(AuthenticationViewController * _Nonnull)authenticationViewController
|
||||||
didLoginWithSession:(MXSession *)session
|
didLoginWithSession:(MXSession *)session
|
||||||
andPassword:(NSString *)password;
|
andPassword:(NSString * _Nullable)password
|
||||||
|
orSSOIdentityProvider:(SSOIdentityProvider * _Nullable)identityProvider;
|
||||||
|
|
||||||
@end;
|
@end;
|
||||||
|
|
|
@ -76,6 +76,10 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0;
|
||||||
|
|
||||||
// Current SSO transaction id used to identify and validate the SSO authentication callback
|
// Current SSO transaction id used to identify and validate the SSO authentication callback
|
||||||
@property (nonatomic, strong) NSString *ssoCallbackTxnId;
|
@property (nonatomic, strong) NSString *ssoCallbackTxnId;
|
||||||
|
/**
|
||||||
|
The SSO provider that was used to successfully complete login, otherwise `nil`.
|
||||||
|
*/
|
||||||
|
@property (nonatomic, readwrite, nullable) SSOIdentityProvider *ssoIdentityProvider;
|
||||||
|
|
||||||
@property (nonatomic, getter = isFirstViewAppearing) BOOL firstViewAppearing;
|
@property (nonatomic, getter = isFirstViewAppearing) BOOL firstViewAppearing;
|
||||||
|
|
||||||
|
@ -1351,7 +1355,10 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ask the coordinator to show the loading spinner whilst waiting.
|
// Ask the coordinator to show the loading spinner whilst waiting.
|
||||||
[self.authVCDelegate authenticationViewController:self didLoginWithSession:session andPassword:self.authInputsView.password];
|
[self.authVCDelegate authenticationViewController:self
|
||||||
|
didLoginWithSession:session
|
||||||
|
andPassword:self.authInputsView.password
|
||||||
|
orSSOIdentityProvider:self.ssoIdentityProvider];
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - MXKAuthInputsViewDelegate
|
#pragma mark - MXKAuthInputsViewDelegate
|
||||||
|
@ -1590,14 +1597,14 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0;
|
||||||
|
|
||||||
#pragma mark - SocialLoginListViewDelegate
|
#pragma mark - SocialLoginListViewDelegate
|
||||||
|
|
||||||
- (void)socialLoginListView:(SocialLoginListView *)socialLoginListView didTapSocialButtonWithIdentifier:(NSString *)identifier
|
- (void)socialLoginListView:(SocialLoginListView *)socialLoginListView didTapSocialButtonWithProvider:(SSOIdentityProvider *)identityProvider
|
||||||
{
|
{
|
||||||
[self presentSSOAuthenticationForIdentityProviderIdentifier:identifier];
|
[self presentSSOAuthenticationForIdentityProvider:identityProvider];
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - SSOIdentityProviderAuthenticationPresenter
|
#pragma mark - SSOIdentityProviderAuthenticationPresenter
|
||||||
|
|
||||||
- (void)presentSSOAuthenticationForIdentityProviderIdentifier:(NSString*)identityProviderIdentifier
|
- (void)presentSSOAuthenticationForIdentityProvider:(SSOIdentityProvider*)identityProvider
|
||||||
{
|
{
|
||||||
NSString *homeServerStringURL = self.homeServerTextField.text;
|
NSString *homeServerStringURL = self.homeServerTextField.text;
|
||||||
|
|
||||||
|
@ -1615,7 +1622,7 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0;
|
||||||
// Generate a unique identifier that will identify the success callback URL
|
// Generate a unique identifier that will identify the success callback URL
|
||||||
NSString *transactionId = [MXTools generateTransactionId];
|
NSString *transactionId = [MXTools generateTransactionId];
|
||||||
|
|
||||||
[presenter presentForIdentityProviderIdentifier:identityProviderIdentifier with: transactionId from:self animated:YES];
|
[presenter presentForIdentityProvider:identityProvider with: transactionId from:self animated:YES];
|
||||||
|
|
||||||
self.ssoCallbackTxnId = transactionId;
|
self.ssoCallbackTxnId = transactionId;
|
||||||
self.ssoAuthenticationPresenter = presenter;
|
self.ssoAuthenticationPresenter = presenter;
|
||||||
|
@ -1623,7 +1630,7 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0;
|
||||||
|
|
||||||
- (void)presentDefaultSSOAuthentication
|
- (void)presentDefaultSSOAuthentication
|
||||||
{
|
{
|
||||||
[self presentSSOAuthenticationForIdentityProviderIdentifier:nil];
|
[self presentSSOAuthenticationForIdentityProvider:nil];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)dismissSSOAuthenticationPresenter
|
- (void)dismissSSOAuthenticationPresenter
|
||||||
|
@ -1656,8 +1663,11 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0;
|
||||||
[self.errorPresenter presentErrorFromViewController:self forError:error animated:YES handler:nil];
|
[self.errorPresenter presentErrorFromViewController:self forError:error animated:YES handler:nil];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)ssoAuthenticationPresenter:(SSOAuthenticationPresenter *)presenter authenticationSucceededWithToken:(NSString *)token
|
- (void)ssoAuthenticationPresenter:(SSOAuthenticationPresenter *)presenter
|
||||||
|
authenticationSucceededWithToken:(NSString *)token
|
||||||
|
usingIdentityProvider:(SSOIdentityProvider * _Nullable)identityProvider
|
||||||
{
|
{
|
||||||
|
self.ssoIdentityProvider = identityProvider;
|
||||||
[self dismissSSOAuthenticationPresenter];
|
[self dismissSSOAuthenticationPresenter];
|
||||||
[self loginWithToken:token];
|
[self loginWithToken:token];
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,8 +81,8 @@ final class LegacyAuthenticationCoordinator: NSObject, AuthenticationCoordinator
|
||||||
return self.authenticationViewController
|
return self.authenticationViewController
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(authenticationType: MXKAuthenticationType) {
|
func update(authenticationFlow: AuthenticationFlow) {
|
||||||
authenticationViewController.authType = authenticationType
|
authenticationViewController.authType = authenticationFlow.mxkType
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(externalRegistrationParameters: [AnyHashable: Any]) {
|
func update(externalRegistrationParameters: [AnyHashable: Any]) {
|
||||||
|
@ -149,7 +149,16 @@ final class LegacyAuthenticationCoordinator: NSObject, AuthenticationCoordinator
|
||||||
|
|
||||||
// MARK: - AuthenticationViewControllerDelegate
|
// MARK: - AuthenticationViewControllerDelegate
|
||||||
extension LegacyAuthenticationCoordinator: AuthenticationViewControllerDelegate {
|
extension LegacyAuthenticationCoordinator: AuthenticationViewControllerDelegate {
|
||||||
func authenticationViewController(_ authenticationViewController: AuthenticationViewController!, didLoginWith session: MXSession!, andPassword password: String!) {
|
func authenticationViewController(_ authenticationViewController: AuthenticationViewController,
|
||||||
|
didLoginWith session: MXSession!,
|
||||||
|
andPassword password: String?,
|
||||||
|
orSSOIdentityProvider identityProvider: SSOIdentityProvider?) {
|
||||||
|
// Sanity check
|
||||||
|
guard let session = session else {
|
||||||
|
MXLog.failure("[LegacyAuthenticationCoordinator] authenticationViewController(_:didLoginWith:) The MXSession should not be nil.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
self.session = session
|
self.session = session
|
||||||
|
|
||||||
if canPresentAdditionalScreens {
|
if canPresentAdditionalScreens {
|
||||||
|
@ -177,8 +186,18 @@ extension LegacyAuthenticationCoordinator: AuthenticationViewControllerDelegate
|
||||||
verificationListener.start()
|
verificationListener.start()
|
||||||
self.verificationListener = verificationListener
|
self.verificationListener = verificationListener
|
||||||
|
|
||||||
|
let authenticationType: AuthenticationType
|
||||||
|
if let identityProvider = identityProvider {
|
||||||
|
authenticationType = .sso(identityProvider)
|
||||||
|
} else if !password.isEmptyOrNil {
|
||||||
|
authenticationType = .password
|
||||||
|
} else {
|
||||||
|
authenticationType = .other
|
||||||
|
}
|
||||||
|
|
||||||
completion?(.didLogin(session: session, authenticationType: authenticationViewController.authType))
|
completion?(.didLogin(session: session,
|
||||||
|
authenticationFlow: authenticationViewController.authType.flow,
|
||||||
|
authenticationType: authenticationType))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -210,3 +229,29 @@ extension LegacyAuthenticationCoordinator: UIAdaptivePresentationControllerDeleg
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fileprivate extension AuthenticationFlow {
|
||||||
|
var mxkType: MXKAuthenticationType {
|
||||||
|
switch self {
|
||||||
|
case .login:
|
||||||
|
return .login
|
||||||
|
case .register:
|
||||||
|
return .register
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate extension MXKAuthenticationType {
|
||||||
|
var flow: AuthenticationFlow {
|
||||||
|
switch self {
|
||||||
|
case .register:
|
||||||
|
return .register
|
||||||
|
case .login, .forgotPassword:
|
||||||
|
return .login
|
||||||
|
@unknown default:
|
||||||
|
MXLog.failure("[MXKAuthenticationType] Unknown type exposed to Swift.")
|
||||||
|
return .login
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -20,7 +20,9 @@ import SafariServices
|
||||||
@objc protocol SSOAuthenticationPresenterDelegate {
|
@objc protocol SSOAuthenticationPresenterDelegate {
|
||||||
func ssoAuthenticationPresenterDidCancel(_ presenter: SSOAuthenticationPresenter)
|
func ssoAuthenticationPresenterDidCancel(_ presenter: SSOAuthenticationPresenter)
|
||||||
func ssoAuthenticationPresenter(_ presenter: SSOAuthenticationPresenter, authenticationDidFailWithError error: Error)
|
func ssoAuthenticationPresenter(_ presenter: SSOAuthenticationPresenter, authenticationDidFailWithError error: Error)
|
||||||
func ssoAuthenticationPresenter(_ presenter: SSOAuthenticationPresenter, authenticationSucceededWithToken token: String)
|
func ssoAuthenticationPresenter(_ presenter: SSOAuthenticationPresenter,
|
||||||
|
authenticationSucceededWithToken token: String,
|
||||||
|
usingIdentityProvider identityProvider: SSOIdentityProvider?)
|
||||||
}
|
}
|
||||||
|
|
||||||
enum SSOAuthenticationPresenterError: Error {
|
enum SSOAuthenticationPresenterError: Error {
|
||||||
|
@ -46,6 +48,7 @@ final class SSOAuthenticationPresenter: NSObject {
|
||||||
|
|
||||||
// MARK: Public
|
// MARK: Public
|
||||||
|
|
||||||
|
private(set) var identityProvider: SSOIdentityProvider?
|
||||||
weak var delegate: SSOAuthenticationPresenterDelegate?
|
weak var delegate: SSOAuthenticationPresenterDelegate?
|
||||||
|
|
||||||
// MARK: - Setup
|
// MARK: - Setup
|
||||||
|
@ -57,15 +60,16 @@ final class SSOAuthenticationPresenter: NSObject {
|
||||||
|
|
||||||
// MARK: - Public
|
// MARK: - Public
|
||||||
|
|
||||||
func present(forIdentityProviderIdentifier identityProviderIdentifier: String?,
|
func present(forIdentityProvider identityProvider: SSOIdentityProvider?,
|
||||||
with transactionId: String,
|
with transactionId: String,
|
||||||
from presentingViewController: UIViewController,
|
from presentingViewController: UIViewController,
|
||||||
animated: Bool) {
|
animated: Bool) {
|
||||||
guard let authenticationURL = self.ssoAuthenticationService.authenticationURL(for: identityProviderIdentifier, transactionId: transactionId) else {
|
guard let authenticationURL = self.ssoAuthenticationService.authenticationURL(for: identityProvider?.id, transactionId: transactionId) else {
|
||||||
self.delegate?.ssoAuthenticationPresenter(self, authenticationDidFailWithError: SSOAuthenticationPresenterError.failToLoadAuthenticationURL)
|
self.delegate?.ssoAuthenticationPresenter(self, authenticationDidFailWithError: SSOAuthenticationPresenterError.failToLoadAuthenticationURL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.identityProvider = identityProvider
|
||||||
self.presentingViewController = presentingViewController
|
self.presentingViewController = presentingViewController
|
||||||
|
|
||||||
// NOTE: By using SFAuthenticationSession the consent alert show product name instead of display name. Fallback to SFSafariViewController instead in order to not disturb users with "Riot" wording at the moment.
|
// NOTE: By using SFAuthenticationSession the consent alert show product name instead of display name. Fallback to SFSafariViewController instead in order to not disturb users with "Riot" wording at the moment.
|
||||||
|
@ -130,7 +134,7 @@ final class SSOAuthenticationPresenter: NSObject {
|
||||||
}
|
}
|
||||||
} else if let successURL = callBackURL {
|
} else if let successURL = callBackURL {
|
||||||
if let loginToken = self.ssoAuthenticationService.loginToken(from: successURL) {
|
if let loginToken = self.ssoAuthenticationService.loginToken(from: successURL) {
|
||||||
self.delegate?.ssoAuthenticationPresenter(self, authenticationSucceededWithToken: loginToken)
|
self.delegate?.ssoAuthenticationPresenter(self, authenticationSucceededWithToken: loginToken, usingIdentityProvider: self.identityProvider)
|
||||||
} else {
|
} else {
|
||||||
MXLog.debug("SSOAuthenticationPresenter: Login token not found")
|
MXLog.debug("SSOAuthenticationPresenter: Login token not found")
|
||||||
self.delegate?.ssoAuthenticationPresenter(self, authenticationDidFailWithError: SSOAuthenticationServiceError.tokenNotFound)
|
self.delegate?.ssoAuthenticationPresenter(self, authenticationDidFailWithError: SSOAuthenticationServiceError.tokenNotFound)
|
||||||
|
|
|
@ -42,7 +42,10 @@ final class SocialLoginButton: UIButton, Themable {
|
||||||
// MARK: Public
|
// MARK: Public
|
||||||
|
|
||||||
var identifier: String? {
|
var identifier: String? {
|
||||||
return self.viewData?.identifier
|
viewData?.identityProvider.id
|
||||||
|
}
|
||||||
|
var identityProvider: SSOIdentityProvider? {
|
||||||
|
viewData?.identityProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Setup
|
// MARK: Setup
|
||||||
|
|
|
@ -71,7 +71,7 @@ class SocialLoginButtonFactory {
|
||||||
|
|
||||||
let title = self.buildButtonTitle(with: identityProvider.name, mode: mode)
|
let title = self.buildButtonTitle(with: identityProvider.name, mode: mode)
|
||||||
|
|
||||||
let viewData = SocialLoginButtonViewData(identifier: identityProvider.identifier,
|
let viewData = SocialLoginButtonViewData(identityProvider: identityProvider.ssoIdentityProvider,
|
||||||
title: title,
|
title: title,
|
||||||
defaultStyle: defaultStyle,
|
defaultStyle: defaultStyle,
|
||||||
themeStyles: styles)
|
themeStyles: styles)
|
||||||
|
|
|
@ -19,8 +19,8 @@ import Foundation
|
||||||
/// SocialLoginButton view data
|
/// SocialLoginButton view data
|
||||||
struct SocialLoginButtonViewData {
|
struct SocialLoginButtonViewData {
|
||||||
|
|
||||||
/// Identify provider identifier
|
/// Identity provider identifier
|
||||||
let identifier: String
|
let identityProvider: SSOIdentityProvider
|
||||||
|
|
||||||
/// Button title
|
/// Button title
|
||||||
let title: String
|
let title: String
|
||||||
|
|
|
@ -18,7 +18,7 @@ import UIKit
|
||||||
import Reusable
|
import Reusable
|
||||||
|
|
||||||
@objc protocol SocialLoginListViewDelegate: AnyObject {
|
@objc protocol SocialLoginListViewDelegate: AnyObject {
|
||||||
func socialLoginListView(_ socialLoginListView: SocialLoginListView, didTapSocialButtonWithIdentifier identifier: String)
|
func socialLoginListView(_ socialLoginListView: SocialLoginListView, didTapSocialButtonWithProvider identityProvider: SSOIdentityProvider)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// SocialLoginListView displays a list of social login buttons according to a given array of SSO Identity Providers.
|
/// SocialLoginListView displays a list of social login buttons according to a given array of SSO Identity Providers.
|
||||||
|
@ -139,10 +139,10 @@ final class SocialLoginListView: UIView, NibLoadable {
|
||||||
// MARK: - Action
|
// MARK: - Action
|
||||||
|
|
||||||
@objc private func socialButtonAction(_ socialLoginButton: SocialLoginButton) {
|
@objc private func socialButtonAction(_ socialLoginButton: SocialLoginButton) {
|
||||||
guard let identifier = socialLoginButton.identifier else {
|
guard let provider = socialLoginButton.identityProvider else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.delegate?.socialLoginListView(self, didTapSocialButtonWithIdentifier: identifier)
|
self.delegate?.socialLoginListView(self, didTapSocialButtonWithProvider: provider)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -76,7 +76,7 @@ final class AuthenticationCoordinator: NSObject, AuthenticationCoordinatorProtoc
|
||||||
func start() {
|
func start() {
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
let flow: AuthenticationFlow = initialScreen == .login ? .login : .registration
|
let flow: AuthenticationFlow = initialScreen == .login ? .login : .register
|
||||||
try await authenticationService.startFlow(flow, for: authenticationService.state.homeserver.address)
|
try await authenticationService.startFlow(flow, for: authenticationService.state.homeserver.address)
|
||||||
} catch {
|
} catch {
|
||||||
MXLog.error("[AuthenticationCoordinator] start: Failed to start")
|
MXLog.error("[AuthenticationCoordinator] start: Failed to start")
|
||||||
|
@ -211,7 +211,7 @@ final class AuthenticationCoordinator: NSObject, AuthenticationCoordinatorProtoc
|
||||||
func handleRegistrationResult(_ result: RegistrationResult) {
|
func handleRegistrationResult(_ result: RegistrationResult) {
|
||||||
switch result {
|
switch result {
|
||||||
case .success(let mxSession):
|
case .success(let mxSession):
|
||||||
onSessionCreated(session: mxSession, isAccountCreated: true)
|
onSessionCreated(session: mxSession, flow: .register)
|
||||||
case .flowResponse(let flowResult):
|
case .flowResponse(let flowResult):
|
||||||
// TODO
|
// TODO
|
||||||
break
|
break
|
||||||
|
@ -219,7 +219,7 @@ final class AuthenticationCoordinator: NSObject, AuthenticationCoordinatorProtoc
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handles the creation of a new session following on from a successful authentication.
|
/// Handles the creation of a new session following on from a successful authentication.
|
||||||
func onSessionCreated(session: MXSession, isAccountCreated: Bool) {
|
func onSessionCreated(session: MXSession, flow: AuthenticationFlow) {
|
||||||
self.session = session
|
self.session = session
|
||||||
// self.password = password
|
// self.password = password
|
||||||
|
|
||||||
|
@ -249,7 +249,8 @@ final class AuthenticationCoordinator: NSObject, AuthenticationCoordinatorProtoc
|
||||||
verificationListener.start()
|
verificationListener.start()
|
||||||
self.verificationListener = verificationListener
|
self.verificationListener = verificationListener
|
||||||
|
|
||||||
completion?(.didLogin(session: session, authenticationType: isAccountCreated ? .register : .login))
|
#warning("Add authentication type to the new flow")
|
||||||
|
completion?(.didLogin(session: session, authenticationFlow: flow, authenticationType: .other))
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Additional Screens
|
// MARK: - Additional Screens
|
||||||
|
@ -331,7 +332,7 @@ extension AuthenticationCoordinator {
|
||||||
set { /* no-op */ }
|
set { /* no-op */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(authenticationType: MXKAuthenticationType) {
|
func update(authenticationFlow: AuthenticationFlow) {
|
||||||
// unused
|
// unused
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
45
Riot/Modules/Onboarding/AuthenticationType+Analytics.swift
Normal file
45
Riot/Modules/Onboarding/AuthenticationType+Analytics.swift
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
//
|
||||||
|
// Copyright 2022 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.
|
||||||
|
//
|
||||||
|
|
||||||
|
import AnalyticsEvents
|
||||||
|
|
||||||
|
extension AuthenticationType {
|
||||||
|
var analyticsType: AnalyticsEvent.Signup.AuthenticationType {
|
||||||
|
switch self {
|
||||||
|
case .password:
|
||||||
|
return .Password
|
||||||
|
case .sso(let provider):
|
||||||
|
guard let brandString = provider.brand else { return .SSO }
|
||||||
|
let brand = MXLoginSSOIdentityProviderBrand(brandString)
|
||||||
|
switch brand {
|
||||||
|
case .apple:
|
||||||
|
return .Apple
|
||||||
|
case .facebook:
|
||||||
|
return .Facebook
|
||||||
|
case .github:
|
||||||
|
return .GitHub
|
||||||
|
case .gitlab:
|
||||||
|
return .GitLab
|
||||||
|
case .google:
|
||||||
|
return .Google
|
||||||
|
default:
|
||||||
|
return .SSO
|
||||||
|
}
|
||||||
|
case .other:
|
||||||
|
return .Other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -63,7 +63,10 @@ final class OnboardingCoordinator: NSObject, OnboardingCoordinatorProtocol {
|
||||||
// MARK: Screen results
|
// MARK: Screen results
|
||||||
private var splashScreenResult: OnboardingSplashScreenViewModelResult?
|
private var splashScreenResult: OnboardingSplashScreenViewModelResult?
|
||||||
private var useCaseResult: OnboardingUseCaseViewModelResult?
|
private var useCaseResult: OnboardingUseCaseViewModelResult?
|
||||||
private var authenticationType: MXKAuthenticationType?
|
/// The flow being used for authentication.
|
||||||
|
private var authenticationFlow: AuthenticationFlow?
|
||||||
|
/// The type of authentication used to login/register.
|
||||||
|
private var authenticationType: AuthenticationType?
|
||||||
private var session: MXSession?
|
private var session: MXSession?
|
||||||
/// A place to store the image selected in the avatar screen until it has been saved.
|
/// A place to store the image selected in the avatar screen until it has been saved.
|
||||||
private var selectedAvatar: UIImage?
|
private var selectedAvatar: UIImage?
|
||||||
|
@ -154,7 +157,7 @@ final class OnboardingCoordinator: NSObject, OnboardingCoordinatorProtocol {
|
||||||
splashScreenResult = result
|
splashScreenResult = result
|
||||||
|
|
||||||
// Set the auth type early to allow network requests to finish during display of the use case screen.
|
// Set the auth type early to allow network requests to finish during display of the use case screen.
|
||||||
authenticationCoordinator.update(authenticationType: result.mxkAuthenticationType)
|
authenticationCoordinator.update(authenticationFlow: result.flow)
|
||||||
|
|
||||||
switch result {
|
switch result {
|
||||||
case .register:
|
case .register:
|
||||||
|
@ -219,8 +222,8 @@ final class OnboardingCoordinator: NSObject, OnboardingCoordinatorProtocol {
|
||||||
guard let self = self, let coordinator = coordinator else { return }
|
guard let self = self, let coordinator = coordinator else { return }
|
||||||
|
|
||||||
switch result {
|
switch result {
|
||||||
case .didLogin(let session, let authenticationType):
|
case .didLogin(let session, let authenticationFlow, let authenticationType):
|
||||||
self.authenticationCoordinator(coordinator, didLoginWith: session, and: authenticationType)
|
self.authenticationCoordinator(coordinator, didLoginWith: session, and: authenticationFlow, using: authenticationType)
|
||||||
case .didComplete:
|
case .didComplete:
|
||||||
self.authenticationCoordinatorDidComplete(coordinator)
|
self.authenticationCoordinatorDidComplete(coordinator)
|
||||||
}
|
}
|
||||||
|
@ -241,8 +244,8 @@ final class OnboardingCoordinator: NSObject, OnboardingCoordinatorProtocol {
|
||||||
guard let self = self, let coordinator = coordinator else { return }
|
guard let self = self, let coordinator = coordinator else { return }
|
||||||
|
|
||||||
switch result {
|
switch result {
|
||||||
case .didLogin(let session, let authenticationType):
|
case .didLogin(let session, let authenticationFlow, let authenticationType):
|
||||||
self.authenticationCoordinator(coordinator, didLoginWith: session, and: authenticationType)
|
self.authenticationCoordinator(coordinator, didLoginWith: session, and: authenticationFlow, using: authenticationType)
|
||||||
case .didComplete:
|
case .didComplete:
|
||||||
self.authenticationCoordinatorDidComplete(coordinator)
|
self.authenticationCoordinatorDidComplete(coordinator)
|
||||||
}
|
}
|
||||||
|
@ -284,13 +287,15 @@ final class OnboardingCoordinator: NSObject, OnboardingCoordinatorProtocol {
|
||||||
/// whilst crypto and the rest of the app is launching in the background.
|
/// whilst crypto and the rest of the app is launching in the background.
|
||||||
private func authenticationCoordinator(_ coordinator: AuthenticationCoordinatorProtocol,
|
private func authenticationCoordinator(_ coordinator: AuthenticationCoordinatorProtocol,
|
||||||
didLoginWith session: MXSession,
|
didLoginWith session: MXSession,
|
||||||
and authenticationType: MXKAuthenticationType) {
|
and authenticationFlow: AuthenticationFlow,
|
||||||
|
using authenticationType: AuthenticationType) {
|
||||||
self.session = session
|
self.session = session
|
||||||
|
self.authenticationFlow = authenticationFlow
|
||||||
self.authenticationType = authenticationType
|
self.authenticationType = authenticationType
|
||||||
|
|
||||||
// Check whether another screen should be shown.
|
// Check whether another screen should be shown.
|
||||||
if #available(iOS 14.0, *) {
|
if #available(iOS 14.0, *) {
|
||||||
if authenticationType == .register,
|
if authenticationFlow == .register,
|
||||||
let userId = session.credentials.userId,
|
let userId = session.credentials.userId,
|
||||||
let userSession = UserSessionsService.shared.userSession(withUserId: userId) {
|
let userSession = UserSessionsService.shared.userSession(withUserId: userId) {
|
||||||
// If personalisation is to be shown, check that the homeserver supports it otherwise show the congratulations screen
|
// If personalisation is to be shown, check that the homeserver supports it otherwise show the congratulations screen
|
||||||
|
@ -335,7 +340,7 @@ final class OnboardingCoordinator: NSObject, OnboardingCoordinatorProtocol {
|
||||||
isShowingLegacyAuthentication = false
|
isShowingLegacyAuthentication = false
|
||||||
|
|
||||||
// Handle the chosen use case where applicable
|
// Handle the chosen use case where applicable
|
||||||
if authenticationType == .register,
|
if authenticationFlow == .register,
|
||||||
let useCase = useCaseResult?.userSessionPropertyValue,
|
let useCase = useCaseResult?.userSessionPropertyValue,
|
||||||
let userSession = UserSessionsService.shared.mainUserSession {
|
let userSession = UserSessionsService.shared.mainUserSession {
|
||||||
// Store the value in the user's session
|
// Store the value in the user's session
|
||||||
|
@ -559,15 +564,28 @@ final class OnboardingCoordinator: NSObject, OnboardingCoordinatorProtocol {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trackSignup()
|
||||||
|
|
||||||
completion?()
|
completion?()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sends a signup event to the Analytics class if onboarding has completed via the register flow.
|
||||||
|
private func trackSignup() {
|
||||||
|
guard authenticationFlow == .register else { return }
|
||||||
|
guard let authenticationType = authenticationType else {
|
||||||
|
MXLog.warning("[OnboardingCoordinator] sendSignedEvent: Registration finished without collecting an authentication type.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Analytics.shared.trackSignup(authenticationType: authenticationType.analyticsType)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Helpers
|
// MARK: - Helpers
|
||||||
|
|
||||||
extension OnboardingSplashScreenViewModelResult {
|
extension OnboardingSplashScreenViewModelResult {
|
||||||
/// The result converted into the MatrixKit authentication type to use.
|
/// The result converted into an authentication flow.
|
||||||
var mxkAuthenticationType: MXKAuthenticationType {
|
var flow: AuthenticationFlow {
|
||||||
switch self {
|
switch self {
|
||||||
case .login:
|
case .login:
|
||||||
return .login
|
return .login
|
||||||
|
|
|
@ -16,10 +16,20 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
/// A value that represents the type of authentication flow being used.
|
/// A value that represents an authentication flow as either login or register.
|
||||||
enum AuthenticationFlow {
|
enum AuthenticationFlow {
|
||||||
case login
|
case login
|
||||||
case registration
|
case register
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A value that represents the type of authentication used.
|
||||||
|
enum AuthenticationType {
|
||||||
|
/// A username and password.
|
||||||
|
case password
|
||||||
|
/// SSO with the associated provider
|
||||||
|
case sso(SSOIdentityProvider)
|
||||||
|
/// Some other method such as the fall back page.
|
||||||
|
case other
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Errors that can be thrown from `AuthenticationService`.
|
/// Errors that can be thrown from `AuthenticationService`.
|
||||||
|
@ -65,8 +75,8 @@ enum LoginError: String, Error {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents an SSO Identity Provider as provided in a login flow.
|
/// Represents an SSO Identity Provider as provided in a login flow.
|
||||||
struct SSOIdentityProvider: Identifiable {
|
@objc class SSOIdentityProvider: NSObject, Identifiable {
|
||||||
/// The identifier field (id field in JSON) is the Identity Provider identifier used for the SSO Web page redirection `/login/sso/redirect/{idp_id}`.
|
/// The id field is the Identity Provider identifier used for the SSO Web page redirection `/login/sso/redirect/{idp_id}`.
|
||||||
let id: String
|
let id: String
|
||||||
/// The name field is a human readable string intended to be printed by the client.
|
/// The name field is a human readable string intended to be printed by the client.
|
||||||
let name: String
|
let name: String
|
||||||
|
@ -74,4 +84,11 @@ struct SSOIdentityProvider: Identifiable {
|
||||||
let brand: String?
|
let brand: String?
|
||||||
/// The icon field is an optional field that points to an icon representing the identity provider. If present then it must be an HTTPS URL to an image resource.
|
/// The icon field is an optional field that points to an icon representing the identity provider. If present then it must be an HTTPS URL to an image resource.
|
||||||
let iconURL: String?
|
let iconURL: String?
|
||||||
|
|
||||||
|
init(id: String, name: String, brand: String?, iconURL: String?) {
|
||||||
|
self.id = id
|
||||||
|
self.name = name
|
||||||
|
self.brand = brand
|
||||||
|
self.iconURL = iconURL
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -98,7 +98,7 @@ class AuthenticationService: NSObject {
|
||||||
let loginWizard = LoginWizard()
|
let loginWizard = LoginWizard()
|
||||||
self.loginWizard = loginWizard
|
self.loginWizard = loginWizard
|
||||||
|
|
||||||
if flow == .registration {
|
if flow == .register {
|
||||||
do {
|
do {
|
||||||
let registrationWizard = RegistrationWizard(client: client)
|
let registrationWizard = RegistrationWizard(client: client)
|
||||||
state.homeserver.registrationFlow = try await registrationWizard.registrationFlow()
|
state.homeserver.registrationFlow = try await registrationWizard.registrationFlow()
|
||||||
|
@ -124,7 +124,7 @@ class AuthenticationService: NSObject {
|
||||||
switch flow {
|
switch flow {
|
||||||
case .login:
|
case .login:
|
||||||
return client.loginFallbackURL
|
return client.loginFallbackURL
|
||||||
case .registration:
|
case .register:
|
||||||
return client.registerFallbackURL
|
return client.registerFallbackURL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -119,7 +119,7 @@ final class AuthenticationServerSelectionCoordinator: Coordinator, Presentable {
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
#warning("The screen should be configuration for .login too.")
|
#warning("The screen should be configuration for .login too.")
|
||||||
try await authenticationService.startFlow(.registration, for: homeserverAddress)
|
try await authenticationService.startFlow(.register, for: homeserverAddress)
|
||||||
stopLoading()
|
stopLoading()
|
||||||
|
|
||||||
completion?(.updated)
|
completion?(.updated)
|
||||||
|
|
|
@ -39,7 +39,7 @@ class AuthenticationServiceTests: XCTestCase {
|
||||||
XCTAssertNil(service.state.homeserver.registrationFlow, "A new service shouldn't provide a registration flow for the homeserver.")
|
XCTAssertNil(service.state.homeserver.registrationFlow, "A new service shouldn't provide a registration flow for the homeserver.")
|
||||||
|
|
||||||
// When starting a new registration flow.
|
// When starting a new registration flow.
|
||||||
try await service.startFlow(.registration, for: "https://matrix.org")
|
try await service.startFlow(.register, for: "https://matrix.org")
|
||||||
|
|
||||||
// Then a registration wizard should be available for use.
|
// Then a registration wizard should be available for use.
|
||||||
XCTAssertNotNil(service.registrationWizard, "The registration wizard should exist after starting a registration flow.")
|
XCTAssertNotNil(service.registrationWizard, "The registration wizard should exist after starting a registration flow.")
|
||||||
|
@ -49,13 +49,13 @@ class AuthenticationServiceTests: XCTestCase {
|
||||||
func testReset() async throws {
|
func testReset() async throws {
|
||||||
// Given a service that has begun registration.
|
// Given a service that has begun registration.
|
||||||
let service = AuthenticationService()
|
let service = AuthenticationService()
|
||||||
try await service.startFlow(.registration, for: "https://matrix.org")
|
try await service.startFlow(.register, for: "https://matrix.org")
|
||||||
_ = try await service.registrationWizard?.createAccount(username: UUID().uuidString, password: UUID().uuidString, initialDeviceDisplayName: "Test")
|
_ = try await service.registrationWizard?.createAccount(username: UUID().uuidString, password: UUID().uuidString, initialDeviceDisplayName: "Test")
|
||||||
XCTAssertNotNil(service.loginWizard, "The login wizard should exist after starting a registration flow.")
|
XCTAssertNotNil(service.loginWizard, "The login wizard should exist after starting a registration flow.")
|
||||||
XCTAssertNotNil(service.registrationWizard, "The registration wizard should exist after starting a registration flow.")
|
XCTAssertNotNil(service.registrationWizard, "The registration wizard should exist after starting a registration flow.")
|
||||||
XCTAssertNotNil(service.state.homeserver.registrationFlow, "The supported registration flow should be stored after starting a registration flow.")
|
XCTAssertNotNil(service.state.homeserver.registrationFlow, "The supported registration flow should be stored after starting a registration flow.")
|
||||||
XCTAssertTrue(service.isRegistrationStarted, "The service should show as having started registration.")
|
XCTAssertTrue(service.isRegistrationStarted, "The service should show as having started registration.")
|
||||||
XCTAssertEqual(service.state.flow, .registration, "The service should show as using a registration flow.")
|
XCTAssertEqual(service.state.flow, .register, "The service should show as using a registration flow.")
|
||||||
|
|
||||||
// When resetting the service.
|
// When resetting the service.
|
||||||
service.reset()
|
service.reset()
|
||||||
|
|
1
changelog.d/6074.change
Normal file
1
changelog.d/6074.change
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Authentication: New user accounts are now tracked in analytics if the user opted in.
|
Loading…
Reference in a new issue