Merge MatrixKit develop with commit hash: b85b736313bec0592bd1cabc68035d97f5331137

This commit is contained in:
SBiOSoftWhare
2021-12-03 11:47:24 +01:00
parent 475fe5552f
commit 1d9f01b5e3
475 changed files with 87437 additions and 0 deletions
@@ -0,0 +1,41 @@
/*
Copyright 2015 OpenMarket 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 "MXKAuthInputsView.h"
@interface MXKAuthInputsEmailCodeBasedView : MXKAuthInputsView
/**
The input text field related to user id or user login.
*/
@property (weak, nonatomic) IBOutlet UITextField *userLoginTextField;
/**
The input text field used to fill an email or the related token.
*/
@property (weak, nonatomic) IBOutlet UITextField *emailAndTokenTextField;
/**
Label used to prompt user to fill the email token.
*/
@property (weak, nonatomic) IBOutlet UILabel *promptEmailTokenLabel;
/**
The text field related to the display name. This item is displayed in case of registration.
*/
@property (weak, nonatomic) IBOutlet UITextField *displayNameTextField;
@end
@@ -0,0 +1,202 @@
/*
Copyright 2015 OpenMarket 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 "MXKAuthInputsEmailCodeBasedView.h"
#import "NSBundle+MatrixKit.h"
#import "MXKSwiftHeader.h"
@implementation MXKAuthInputsEmailCodeBasedView
+ (UINib *)nib
{
return [UINib nibWithNibName:NSStringFromClass([MXKAuthInputsEmailCodeBasedView class])
bundle:[NSBundle bundleForClass:[MXKAuthInputsEmailCodeBasedView class]]];
}
- (void)awakeFromNib
{
[super awakeFromNib];
_userLoginTextField.placeholder = [MatrixKitL10n loginUserIdPlaceholder];
_emailAndTokenTextField.placeholder = [MatrixKitL10n loginEmailPlaceholder];
_promptEmailTokenLabel.text = [MatrixKitL10n loginPromptEmailToken];
_displayNameTextField.placeholder = [MatrixKitL10n loginDisplayNamePlaceholder];
}
#pragma mark -
- (BOOL)setAuthSession:(MXAuthenticationSession *)authSession withAuthType:(MXKAuthenticationType)authType;
{
if (type == MXKAuthenticationTypeLogin || type == MXKAuthenticationTypeRegister)
{
// Validate first the provided session
MXAuthenticationSession *validSession = [self validateAuthenticationSession:authSession];
if ([super setAuthSession:validSession withAuthType:authType])
{
// Set initial layout
self.userLoginTextField.hidden = NO;
self.promptEmailTokenLabel.hidden = YES;
if (type == MXKAuthenticationTypeLogin)
{
self.emailAndTokenTextField.returnKeyType = UIReturnKeyDone;
self.displayNameTextField.hidden = YES;
self.viewHeightConstraint.constant = self.displayNameTextField.frame.origin.y;
}
else
{
self.emailAndTokenTextField.returnKeyType = UIReturnKeyNext;
self.displayNameTextField.hidden = NO;
self.viewHeightConstraint.constant = 122;
}
return YES;
}
}
return NO;
}
- (NSString*)validateParameters
{
NSString *errorMsg = [super validateParameters];
if (!errorMsg)
{
if (!self.areAllRequiredFieldsSet)
{
errorMsg = [MatrixKitL10n loginInvalidParam];
}
}
return errorMsg;
}
- (BOOL)areAllRequiredFieldsSet
{
BOOL ret = [super areAllRequiredFieldsSet];
// Check required fields //FIXME what are required fields in this authentication flow?
ret = (ret && self.userLoginTextField.text.length && self.emailAndTokenTextField.text.length);
return ret;
}
- (void)dismissKeyboard
{
[self.userLoginTextField resignFirstResponder];
[self.emailAndTokenTextField resignFirstResponder];
[self.displayNameTextField resignFirstResponder];
[super dismissKeyboard];
}
- (void)nextStep
{
// Consider here the email token has been requested with success
[super nextStep];
self.userLoginTextField.hidden = YES;
self.promptEmailTokenLabel.hidden = NO;
self.emailAndTokenTextField.placeholder = nil;
self.emailAndTokenTextField.returnKeyType = UIReturnKeyDone;
self.displayNameTextField.hidden = YES;
}
- (NSString*)userId
{
return self.userLoginTextField.text;
}
#pragma mark UITextField delegate
- (BOOL)textFieldShouldReturn:(UITextField*)textField
{
if (textField.returnKeyType == UIReturnKeyDone)
{
// "Done" key has been pressed
[textField resignFirstResponder];
// Launch authentication now
[self.delegate authInputsViewDidPressDoneKey:self];
}
else
{
//"Next" key has been pressed
if (textField == self.userLoginTextField)
{
[self.emailAndTokenTextField becomeFirstResponder];
}
else if (textField == self.emailAndTokenTextField)
{
[self.displayNameTextField becomeFirstResponder];
}
}
return YES;
}
#pragma mark -
- (MXAuthenticationSession*)validateAuthenticationSession:(MXAuthenticationSession*)authSession
{
// Check whether at least one of the listed flow is supported.
BOOL isSupported = NO;
for (MXLoginFlow *loginFlow in authSession.flows)
{
// Check whether flow type is defined
if ([loginFlow.type isEqualToString:kMXLoginFlowTypeEmailCode])
{
isSupported = YES;
break;
}
else if (loginFlow.stages.count == 1 && [loginFlow.stages.firstObject isEqualToString:kMXLoginFlowTypeEmailCode])
{
isSupported = YES;
break;
}
}
if (isSupported)
{
if (authSession.flows.count == 1)
{
// Return the original session.
return authSession;
}
else
{
// Keep only the supported flow.
MXAuthenticationSession *updatedAuthSession = [[MXAuthenticationSession alloc] init];
updatedAuthSession.session = authSession.session;
updatedAuthSession.params = authSession.params;
updatedAuthSession.flows = @[[MXLoginFlow modelFromJSON:@{@"stages":@[kMXLoginFlowTypeEmailCode]}]];
return updatedAuthSession;
}
}
return nil;
}
@end
@@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="11762" systemVersion="16C67" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11757"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Nmn-Ry-t32" customClass="MXKAuthInputsEmailCodeBasedView">
<rect key="frame" x="0.0" y="0.0" width="300" height="122"/>
<subviews>
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="Matrix ID (e.g. @bob:matrix.org or bob)" textAlignment="center" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="oen-Yq-xJw">
<rect key="frame" x="14" y="8" width="272" height="30"/>
<constraints>
<constraint firstAttribute="height" constant="30" id="gfu-Ef-79U"/>
<constraint firstAttribute="width" constant="272" id="y4N-L3-qlh"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits" autocorrectionType="no" returnKeyType="next"/>
<connections>
<outlet property="delegate" destination="Nmn-Ry-t32" id="Ari-Ab-eGE"/>
</connections>
</textField>
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="Email address" textAlignment="center" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="mbS-mV-L86" userLabel="Email-Token">
<rect key="frame" x="14" y="46" width="272" height="30"/>
<constraints>
<constraint firstAttribute="height" constant="30" id="ecT-NT-FqH"/>
<constraint firstAttribute="width" constant="272" id="xkx-sr-F8M"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits" autocorrectionType="no" returnKeyType="done" secureTextEntry="YES"/>
<connections>
<outlet property="delegate" destination="Nmn-Ry-t32" id="yrA-lO-OCi"/>
</connections>
</textField>
<label hidden="YES" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Please enter your email validation token:" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="vDa-DE-6Bn">
<rect key="frame" x="20" y="8" width="260" height="30"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<textField hidden="YES" opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="Display name (e.g. Bob Obson)" textAlignment="center" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="FVM-ec-tja" userLabel="DisplayName">
<rect key="frame" x="14" y="84" width="272" height="30"/>
<constraints>
<constraint firstAttribute="width" constant="272" id="1b5-Ej-eUB"/>
<constraint firstAttribute="height" constant="30" id="bEU-4F-cUv"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits" autocorrectionType="no" returnKeyType="done" secureTextEntry="YES"/>
<connections>
<outlet property="delegate" destination="Nmn-Ry-t32" id="Q3D-XL-8Ke"/>
</connections>
</textField>
</subviews>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="FVM-ec-tja" firstAttribute="top" secondItem="mbS-mV-L86" secondAttribute="bottom" constant="8" id="0BV-8c-ZVg"/>
<constraint firstAttribute="height" constant="122" id="1nP-RR-5GK"/>
<constraint firstAttribute="centerX" secondItem="mbS-mV-L86" secondAttribute="centerX" id="2R2-Du-o97"/>
<constraint firstItem="oen-Yq-xJw" firstAttribute="top" secondItem="Nmn-Ry-t32" secondAttribute="top" constant="8" id="BkW-Np-qB3"/>
<constraint firstItem="mbS-mV-L86" firstAttribute="top" secondItem="oen-Yq-xJw" secondAttribute="bottom" constant="8" id="TRx-gf-Afp"/>
<constraint firstAttribute="centerX" secondItem="FVM-ec-tja" secondAttribute="centerX" id="c9K-6H-ufd"/>
<constraint firstItem="mbS-mV-L86" firstAttribute="top" secondItem="vDa-DE-6Bn" secondAttribute="bottom" constant="8" id="cq8-qs-59M"/>
<constraint firstItem="vDa-DE-6Bn" firstAttribute="top" secondItem="Nmn-Ry-t32" secondAttribute="top" constant="8" id="eAp-Ob-hFr"/>
<constraint firstAttribute="centerX" secondItem="vDa-DE-6Bn" secondAttribute="centerX" id="otm-qx-iNL"/>
<constraint firstAttribute="centerX" secondItem="oen-Yq-xJw" secondAttribute="centerX" id="s7Y-qf-yrY"/>
</constraints>
<nil key="simulatedStatusBarMetrics"/>
<nil key="simulatedTopBarMetrics"/>
<nil key="simulatedBottomBarMetrics"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<connections>
<outlet property="displayNameTextField" destination="FVM-ec-tja" id="pMF-oP-7Jj"/>
<outlet property="emailAndTokenTextField" destination="mbS-mV-L86" id="nY5-gT-wGe"/>
<outlet property="promptEmailTokenLabel" destination="vDa-DE-6Bn" id="X3w-5E-0Kg"/>
<outlet property="userLoginTextField" destination="oen-Yq-xJw" id="OM2-gy-O9P"/>
<outlet property="viewHeightConstraint" destination="1nP-RR-5GK" id="s3W-1L-bWN"/>
</connections>
</view>
</objects>
</document>
@@ -0,0 +1,46 @@
/*
Copyright 2015 OpenMarket 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 "MXKAuthInputsView.h"
@interface MXKAuthInputsPasswordBasedView : MXKAuthInputsView
/**
The input text field related to user id or user login.
*/
@property (weak, nonatomic) IBOutlet UITextField *userLoginTextField;
/**
The input text field used to fill the password.
*/
@property (weak, nonatomic) IBOutlet UITextField *passWordTextField;
/**
The input text field used to fill an email. This item is optional, it is added in case of registration.
*/
@property (weak, nonatomic) IBOutlet UITextField *emailTextField;
/**
Label used to display email field information.
*/
@property (weak, nonatomic) IBOutlet UILabel *emailInfoLabel;
/**
The text field related to the display name. This item is displayed in case of registration.
*/
@property (weak, nonatomic) IBOutlet UITextField *displayNameTextField;
@end
@@ -0,0 +1,253 @@
/*
Copyright 2015 OpenMarket Ltd
Copyright 2017 Vector Creations 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 "MXKAuthInputsPasswordBasedView.h"
#import "MXKTools.h"
#import "MXKAppSettings.h"
#import "NSBundle+MatrixKit.h"
#import "MXKSwiftHeader.h"
@implementation MXKAuthInputsPasswordBasedView
+ (UINib *)nib
{
return [UINib nibWithNibName:NSStringFromClass([MXKAuthInputsPasswordBasedView class])
bundle:[NSBundle bundleForClass:[MXKAuthInputsPasswordBasedView class]]];
}
- (void)awakeFromNib
{
[super awakeFromNib];
_userLoginTextField.placeholder = [MatrixKitL10n loginUserIdPlaceholder];
_passWordTextField.placeholder = [MatrixKitL10n loginPasswordPlaceholder];
_emailTextField.placeholder = [NSString stringWithFormat:@"%@ (%@)", [MatrixKitL10n loginEmailPlaceholder], [MatrixKitL10n loginOptionalField]];
_emailInfoLabel.text = [MatrixKitL10n loginEmailInfo];
_displayNameTextField.placeholder = [MatrixKitL10n loginDisplayNamePlaceholder];
}
#pragma mark -
- (BOOL)setAuthSession:(MXAuthenticationSession *)authSession withAuthType:(MXKAuthenticationType)authType;
{
if (type == MXKAuthenticationTypeLogin || type == MXKAuthenticationTypeRegister)
{
// Validate first the provided session
MXAuthenticationSession *validSession = [self validateAuthenticationSession:authSession];
if ([super setAuthSession:validSession withAuthType:authType])
{
if (type == MXKAuthenticationTypeLogin)
{
self.passWordTextField.returnKeyType = UIReturnKeyDone;
self.emailTextField.hidden = YES;
self.emailInfoLabel.hidden = YES;
self.displayNameTextField.hidden = YES;
self.viewHeightConstraint.constant = self.displayNameTextField.frame.origin.y;
}
else
{
self.passWordTextField.returnKeyType = UIReturnKeyNext;
self.emailTextField.hidden = NO;
self.emailInfoLabel.hidden = NO;
self.displayNameTextField.hidden = NO;
self.viewHeightConstraint.constant = 179;
}
return YES;
}
}
return NO;
}
- (NSString*)validateParameters
{
NSString *errorMsg = [super validateParameters];
if (!errorMsg)
{
// Check user login and pass fields
if (!self.areAllRequiredFieldsSet)
{
errorMsg = [MatrixKitL10n loginInvalidParam];
}
}
return errorMsg;
}
- (void)prepareParameters:(void (^)(NSDictionary *parameters, NSError *error))callback
{
if (callback)
{
// Sanity check on required fields
if (!self.areAllRequiredFieldsSet)
{
callback(nil, [NSError errorWithDomain:MXKAuthErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey:[MatrixKitL10n loginInvalidParam]}]);
return;
}
// Retrieve the user login and check whether it is an email or a username.
// TODO: Update the UI view to support the login based on a mobile phone number.
NSString *user = self.userLoginTextField.text;
user = [user stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
BOOL isEmailAddress = [MXTools isEmailAddress:user];
NSDictionary *parameters;
if (isEmailAddress)
{
parameters = @{
@"type": kMXLoginFlowTypePassword,
@"identifier": @{
@"type": kMXLoginIdentifierTypeThirdParty,
@"medium": kMX3PIDMediumEmail,
@"address": user
},
@"password": self.passWordTextField.text
};
}
else
{
parameters = @{
@"type": kMXLoginFlowTypePassword,
@"identifier": @{
@"type": kMXLoginIdentifierTypeUser,
@"user": user
},
@"password": self.passWordTextField.text
};
}
callback(parameters, nil);
}
}
- (BOOL)areAllRequiredFieldsSet
{
BOOL ret = [super areAllRequiredFieldsSet];
// Check user login and pass fields
ret = (ret && self.userLoginTextField.text.length && self.passWordTextField.text.length);
return ret;
}
- (void)dismissKeyboard
{
[self.userLoginTextField resignFirstResponder];
[self.passWordTextField resignFirstResponder];
[self.emailTextField resignFirstResponder];
[self.displayNameTextField resignFirstResponder];
[super dismissKeyboard];
}
- (NSString*)userId
{
return self.userLoginTextField.text;
}
- (NSString*)password
{
return self.passWordTextField.text;
}
#pragma mark UITextField delegate
- (BOOL)textFieldShouldReturn:(UITextField*)textField
{
if (textField.returnKeyType == UIReturnKeyDone)
{
// "Done" key has been pressed
[textField resignFirstResponder];
// Launch authentication now
[self.delegate authInputsViewDidPressDoneKey:self];
}
else
{
//"Next" key has been pressed
if (textField == self.userLoginTextField)
{
[self.passWordTextField becomeFirstResponder];
}
else if (textField == self.passWordTextField)
{
[self.displayNameTextField becomeFirstResponder];
}
else if (textField == self.displayNameTextField)
{
[self.emailTextField becomeFirstResponder];
}
}
return YES;
}
#pragma mark -
- (MXAuthenticationSession*)validateAuthenticationSession:(MXAuthenticationSession*)authSession
{
// Check whether at least one of the listed flow is supported.
BOOL isSupported = NO;
for (MXLoginFlow *loginFlow in authSession.flows)
{
// Check whether flow type is defined
if ([loginFlow.type isEqualToString:kMXLoginFlowTypePassword])
{
isSupported = YES;
break;
}
else if (loginFlow.stages.count == 1 && [loginFlow.stages.firstObject isEqualToString:kMXLoginFlowTypePassword])
{
isSupported = YES;
break;
}
}
if (isSupported)
{
if (authSession.flows.count == 1)
{
// Return the original session.
return authSession;
}
else
{
// Keep only the supported flow.
MXAuthenticationSession *updatedAuthSession = [[MXAuthenticationSession alloc] init];
updatedAuthSession.session = authSession.session;
updatedAuthSession.params = authSession.params;
updatedAuthSession.flows = @[[MXLoginFlow modelFromJSON:@{@"stages":@[kMXLoginFlowTypePassword]}]];
return updatedAuthSession;
}
}
return nil;
}
@end
@@ -0,0 +1,102 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="11762" systemVersion="16C67" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11757"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="x74-04-ezp" customClass="MXKAuthInputsPasswordBasedView">
<rect key="frame" x="0.0" y="0.0" width="300" height="179"/>
<subviews>
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="Matrix ID (e.g. @bob:matrix.org or bob)" textAlignment="center" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="i51-ym-i9T">
<rect key="frame" x="14" y="8" width="272" height="30"/>
<constraints>
<constraint firstAttribute="width" constant="272" id="IhR-uY-MJZ"/>
<constraint firstAttribute="height" constant="30" id="x3j-uC-j0U"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits" autocorrectionType="no" returnKeyType="next"/>
<connections>
<outlet property="delegate" destination="x74-04-ezp" id="yCp-R2-dNp"/>
</connections>
</textField>
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="Password" textAlignment="center" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="6rs-rR-DkS">
<rect key="frame" x="14" y="46" width="272" height="30"/>
<constraints>
<constraint firstAttribute="height" constant="30" id="Fh2-oZ-UBe"/>
<constraint firstAttribute="width" constant="272" id="pCs-je-5Rd"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits" autocorrectionType="no" returnKeyType="done" secureTextEntry="YES"/>
<connections>
<outlet property="delegate" destination="x74-04-ezp" id="t8U-qP-IHU"/>
</connections>
</textField>
<textField hidden="YES" opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="Display name (e.g. Bob Obson)" textAlignment="center" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="BQM-LP-8Eq" userLabel="DisplayName">
<rect key="frame" x="14" y="84" width="272" height="30"/>
<constraints>
<constraint firstAttribute="height" constant="30" id="Iwg-IP-ITp"/>
<constraint firstAttribute="width" constant="272" id="ZG2-Aw-XkP"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits" autocorrectionType="no" returnKeyType="next" secureTextEntry="YES"/>
<connections>
<outlet property="delegate" destination="x74-04-ezp" id="qkT-a6-G0Y"/>
</connections>
</textField>
<textField hidden="YES" opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="Email address (optional)" textAlignment="center" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="odF-W1-Vdr" userLabel="Email">
<rect key="frame" x="14" y="126" width="272" height="30"/>
<constraints>
<constraint firstAttribute="width" constant="272" id="wZg-GR-5XL"/>
<constraint firstAttribute="height" constant="30" id="zVy-8W-zmR"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits" autocorrectionType="no" returnKeyType="done" secureTextEntry="YES"/>
<connections>
<outlet property="delegate" destination="x74-04-ezp" id="isi-U5-D9A"/>
</connections>
</textField>
<label hidden="YES" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" preferredMaxLayoutWidth="0.0" translatesAutoresizingMaskIntoConstraints="NO" id="OHZ-Ye-nch" userLabel="EmailInfoLabel">
<rect key="frame" x="14" y="156" width="272" height="15"/>
<string key="text">Specify an email address lets other users find you on Matrix more easily, and will give you a way to reset your password in the future.</string>
<fontDescription key="fontDescription" type="system" pointSize="12"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="i51-ym-i9T" firstAttribute="top" secondItem="x74-04-ezp" secondAttribute="top" constant="8" id="2sO-Wi-AFi"/>
<constraint firstItem="BQM-LP-8Eq" firstAttribute="top" secondItem="6rs-rR-DkS" secondAttribute="bottom" constant="8" id="2vZ-NH-f0H"/>
<constraint firstAttribute="centerX" secondItem="odF-W1-Vdr" secondAttribute="centerX" id="BbL-ex-otM"/>
<constraint firstItem="OHZ-Ye-nch" firstAttribute="leading" secondItem="x74-04-ezp" secondAttribute="leading" constant="14" id="Ctv-P7-FQl"/>
<constraint firstItem="6rs-rR-DkS" firstAttribute="top" secondItem="i51-ym-i9T" secondAttribute="bottom" constant="8" id="EJf-md-dcj"/>
<constraint firstAttribute="height" constant="179" id="JbD-Ce-bTY"/>
<constraint firstItem="OHZ-Ye-nch" firstAttribute="top" secondItem="odF-W1-Vdr" secondAttribute="bottom" id="LTI-Eq-pH6"/>
<constraint firstAttribute="centerX" secondItem="i51-ym-i9T" secondAttribute="centerX" id="WKP-y0-Jon"/>
<constraint firstAttribute="centerX" secondItem="BQM-LP-8Eq" secondAttribute="centerX" id="aQQ-kb-tLS"/>
<constraint firstAttribute="centerX" secondItem="6rs-rR-DkS" secondAttribute="centerX" id="nIk-ev-cFe"/>
<constraint firstItem="odF-W1-Vdr" firstAttribute="top" secondItem="BQM-LP-8Eq" secondAttribute="bottom" constant="12" id="qjk-LC-4NN"/>
<constraint firstAttribute="centerX" secondItem="OHZ-Ye-nch" secondAttribute="centerX" id="tvZ-5z-KAm"/>
<constraint firstAttribute="trailing" secondItem="OHZ-Ye-nch" secondAttribute="trailing" constant="14" id="yQg-6n-P8L"/>
</constraints>
<nil key="simulatedStatusBarMetrics"/>
<nil key="simulatedTopBarMetrics"/>
<nil key="simulatedBottomBarMetrics"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<connections>
<outlet property="displayNameTextField" destination="BQM-LP-8Eq" id="Ptq-yP-WaZ"/>
<outlet property="emailInfoLabel" destination="OHZ-Ye-nch" id="fh6-Sz-X5K"/>
<outlet property="emailTextField" destination="odF-W1-Vdr" id="DOS-H7-MZy"/>
<outlet property="passWordTextField" destination="6rs-rR-DkS" id="VeL-kt-Fpp"/>
<outlet property="userLoginTextField" destination="i51-ym-i9T" id="XKi-6m-tFv"/>
<outlet property="viewHeightConstraint" destination="JbD-Ce-bTY" id="09n-jN-RM1"/>
</connections>
</view>
</objects>
</document>
@@ -0,0 +1,242 @@
/*
Copyright 2015 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
Copyright 2019 The Matrix.org Foundation C.I.C
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 <MatrixSDK/MatrixSDK.h>
#import "MXKView.h"
extern NSString *const MXKAuthErrorDomain;
/**
Authentication types
*/
typedef enum {
/**
Type used to sign up.
*/
MXKAuthenticationTypeRegister,
/**
Type used to sign in.
*/
MXKAuthenticationTypeLogin,
/**
Type used to restore an existing account by reseting the password.
*/
MXKAuthenticationTypeForgotPassword
} MXKAuthenticationType;
@class MXKAuthInputsView;
/**
`MXKAuthInputsView` delegate
*/
@protocol MXKAuthInputsViewDelegate <NSObject>
/**
Tells the delegate that an alert must be presented.
@param authInputsView the authentication inputs view.
@param alert the alert to present.
*/
- (void)authInputsView:(MXKAuthInputsView*)authInputsView presentAlertController:(UIAlertController*)alert;
/**
For some input fields, the return key of the keyboard is defined as `Done` key.
By this method, the delegate is notified when this key is pressed.
*/
- (void)authInputsViewDidPressDoneKey:(MXKAuthInputsView *)authInputsView;
@optional
/**
The matrix REST Client used to validate third-party identifiers.
*/
- (MXRestClient *)authInputsViewThirdPartyIdValidationRestClient:(MXKAuthInputsView *)authInputsView;
/**
The identity service used to validate third-party identifiers.
*/
- (MXIdentityService *)authInputsViewThirdPartyIdValidationIdentityService:(MXKAuthInputsView *)authInputsView;
/**
Tell the delegate to present a view controller modally.
Note: This method is used to display the countries list during the phone number handling.
@param authInputsView the authentication inputs view.
@param viewControllerToPresent the view controller to present.
@param animated YES to animate the presentation.
*/
- (void)authInputsView:(MXKAuthInputsView *)authInputsView presentViewController:(UIViewController*)viewControllerToPresent animated:(BOOL)animated;
/**
Tell the delegate to cancel the current operation.
*/
- (void)authInputsViewDidCancelOperation:(MXKAuthInputsView *)authInputsView;
/**
Tell the delegate to autodiscover the server configuration.
*/
- (void)authInputsView:(MXKAuthInputsView *)authInputsView autoDiscoverServerWithDomain:(NSString*)domain;
@end
/**
`MXKAuthInputsView` is a base class to handle authentication inputs.
*/
@interface MXKAuthInputsView : MXKView <UITextFieldDelegate>
{
@protected
/**
The authentication type (`MXKAuthenticationTypeLogin` by default).
*/
MXKAuthenticationType type;
/**
The authentication session (nil by default).
*/
MXAuthenticationSession *currentSession;
/**
Alert used to display inputs error.
*/
UIAlertController *inputsAlert;
}
/**
The view delegate.
*/
@property (nonatomic, weak) id <MXKAuthInputsViewDelegate> delegate;
/**
The current authentication type (`MXKAuthenticationTypeLogin` by default).
*/
@property (nonatomic, readonly) MXKAuthenticationType authType;
/**
The current authentication session if any.
*/
@property (nonatomic, readonly) MXAuthenticationSession *authSession;
/**
The current filled user identifier (nil by default).
*/
@property (nonatomic, readonly) NSString *userId;
/**
The current filled password (nil by default).
*/
@property (nonatomic, readonly) NSString *password;
/**
The layout constraint defined on the view height. This height takes into account shown/hidden fields.
*/
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *viewHeightConstraint;
/**
Returns the `UINib` object initialized for the auth inputs view.
@return The initialized `UINib` object or `nil` if there were errors during
initialization or the nib file could not be located.
*/
+ (UINib *)nib;
/**
Creates and returns a new `MXKAuthInputsView` object.
@discussion This is the designated initializer for programmatic instantiation.
@return An initialized `MXKAuthInputsView` object if successful, `nil` otherwise.
*/
+ (instancetype)authInputsView;
/**
Finalize the authentication inputs view with a session and a type.
Use this method to restore the view in its initial step.
@discussion You may override this method to check/update the flows listed in the provided authentication session.
@param authSession the authentication session returned by the homeserver.
@param authType the authentication type (see 'MXKAuthenticationType').
@return YES if the provided session and type are supported by the MXKAuthInputsView-inherited class. Note the unsupported flows should be here removed from the stored authentication session (see the resulting session in the property named 'authSession').
*/
- (BOOL)setAuthSession:(MXAuthenticationSession *)authSession withAuthType:(MXKAuthenticationType)authType;
/**
Check the validity of the required parameters.
@return an error message in case of wrong parameters (nil by default).
*/
- (NSString*)validateParameters;
/**
Prepare the set of the inputs in order to launch an authentication process.
@param callback the block called when the parameters are prepared. The resulting parameter dictionary is nil
if something fails (for example when a parameter or a required input is missing). The failure reason is provided in error parameter (nil by default).
*/
- (void)prepareParameters:(void (^)(NSDictionary *parameters, NSError *error))callback;
/**
Update the current authentication session by providing the list of successful stages.
@param completedStages the list of stages the client has completed successfully. This is an array of MXLoginFlowType.
@param callback the block called when the parameters have been updated for the next stage. The resulting parameter dictionary is nil
if something fails (for example when a parameter or a required input is missing). The failure reason is provided in error parameter (nil by default).
*/
- (void)updateAuthSessionWithCompletedStages:(NSArray *)completedStages didUpdateParameters:(void (^)(NSDictionary *parameters, NSError *error))callback;
/**
Update the current authentication session by providing a set of registration parameters.
@discussion This operation failed if the current authentication type is MXKAuthenticationTypeLogin.
@param registrationParameters a set of parameters to use during the current registration process.
@return YES if the provided set of parameters is supported.
*/
- (BOOL)setExternalRegistrationParameters:(NSDictionary *)registrationParameters;
/**
Update the current authentication session by providing soft logout credentials.
*/
@property (nonatomic) MXCredentials *softLogoutCredentials;
/**
Tell whether all required fields are set
*/
- (BOOL)areAllRequiredFieldsSet;
/**
Force dismiss keyboard
*/
- (void)dismissKeyboard;
/**
Switch in next authentication flow step by updating the layout.
@discussion This method is supposed to be called only if the current operation succeeds.
*/
- (void)nextStep;
/**
Dispose any resources and listener.
*/
- (void)destroy;
@end
@@ -0,0 +1,156 @@
/*
Copyright 2015 OpenMarket Ltd
Copyright 2017 Vector Creations 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 "MXKAuthInputsView.h"
#import "NSBundle+MatrixKit.h"
#import "MXKSwiftHeader.h"
NSString *const MXKAuthErrorDomain = @"MXKAuthErrorDomain";
@implementation MXKAuthInputsView
+ (UINib *)nib
{
// By default, no nib is available.
return nil;
}
+ (instancetype)authInputsView
{
// Check whether a xib is defined
if ([[self class] nib])
{
return [[[self class] nib] instantiateWithOwner:nil options:nil].firstObject;
}
return [[[self class] alloc] init];
}
- (void)awakeFromNib
{
[super awakeFromNib];
[self setTranslatesAutoresizingMaskIntoConstraints: NO];
type = MXKAuthenticationTypeLogin;
}
- (instancetype)init
{
self = [super init];
if (self)
{
type = MXKAuthenticationTypeLogin;
}
return self;
}
#pragma mark -
- (BOOL)setAuthSession:(MXAuthenticationSession *)authSession withAuthType:(MXKAuthenticationType)authType
{
if (authSession)
{
type = authType;
currentSession = authSession;
return YES;
}
return NO;
}
- (NSString *)validateParameters
{
// Currently no field to check here
return nil;
}
- (void)prepareParameters:(void (^)(NSDictionary *parameters, NSError *error))callback
{
// Do nothing by default
if (callback)
{
callback (nil, [NSError errorWithDomain:MXKAuthErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey:[MatrixKitL10n notSupportedYet]}]);
}
}
- (void)updateAuthSessionWithCompletedStages:(NSArray *)completedStages didUpdateParameters:(void (^)(NSDictionary *parameters, NSError *error))callback
{
// Do nothing by default
if (callback)
{
callback (nil, [NSError errorWithDomain:MXKAuthErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey:[MatrixKitL10n notSupportedYet]}]);
}
}
- (BOOL)setExternalRegistrationParameters:(NSDictionary *)registrationParameters
{
// Not supported by default
return NO;
}
- (BOOL)areAllRequiredFieldsSet
{
// Currently no field to check here
return YES;
}
- (void)dismissKeyboard
{
}
- (void)nextStep
{
}
- (void)destroy
{
if (inputsAlert)
{
[inputsAlert dismissViewControllerAnimated:NO completion:nil];
inputsAlert = nil;
}
}
#pragma mark -
- (MXKAuthenticationType)authType
{
return type;
}
- (MXAuthenticationSession*)authSession
{
return currentSession;
}
- (NSString*)userId
{
return nil;
}
- (NSString*)password
{
return nil;
}
@end
@@ -0,0 +1,30 @@
/*
Copyright 2015 OpenMarket 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 <MatrixSDK/MatrixSDK.h>
#import <WebKit/WebKit.h>
@interface MXKAuthenticationFallbackWebView : WKWebView <WKNavigationDelegate>
/**
Open authentication fallback page into the webview.
@param fallbackPage the fallback page hosted by a homeserver.
@param success the block called when the user has been successfully logged in or registered.
*/
- (void)openFallbackPage:(NSString*)fallbackPage success:(void (^)(MXLoginResponse *loginResponse))success;
@end
@@ -0,0 +1,181 @@
/*
Copyright 2015 OpenMarket 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 "MXKAuthenticationFallbackWebView.h"
// Generic method to make a bridge between JS and the WKWebView
NSString *kMXKJavascriptSendObjectMessage = @"window.sendObjectMessage = function(parameters) { \
var iframe = document.createElement('iframe'); \
iframe.setAttribute('src', 'js:' + JSON.stringify(parameters)); \
\
document.documentElement.appendChild(iframe); \
iframe.parentNode.removeChild(iframe); \
iframe = null; \
};";
// The function the fallback page calls when the registration is complete
NSString *kMXKJavascriptOnRegistered = @"window.matrixRegistration.onRegistered = function(homeserverUrl, userId, accessToken) { \
sendObjectMessage({ \
'action': 'onRegistered', \
'homeServer': homeserverUrl, \
'userId': userId, \
'accessToken': accessToken \
}); \
};";
// The function the fallback page calls when the login is complete
NSString *kMXKJavascriptOnLogin = @"window.matrixLogin.onLogin = function(response) { \
sendObjectMessage({ \
'action': 'onLogin', \
'response': response \
}); \
};";
@interface MXKAuthenticationFallbackWebView ()
{
// The block called when the login or the registration is successful
void (^onSuccess)(MXLoginResponse *);
// Activity indicator
UIActivityIndicatorView *activityIndicator;
}
@end
@implementation MXKAuthenticationFallbackWebView
- (void)dealloc
{
if (activityIndicator)
{
[activityIndicator removeFromSuperview];
activityIndicator = nil;
}
}
- (void)openFallbackPage:(NSString *)fallbackPage success:(void (^)(MXLoginResponse *))success
{
self.navigationDelegate = self;
onSuccess = success;
// Add activity indicator
activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
activityIndicator.center = self.center;
[self addSubview:activityIndicator];
[activityIndicator startAnimating];
// Delete cookies to launch login process from scratch
for(NSHTTPCookie *cookie in [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies])
{
[[NSHTTPCookieStorage sharedHTTPCookieStorage] deleteCookie:cookie];
}
[self loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:fallbackPage]]];
}
#pragma mark - WKNavigationDelegate
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
{
if (activityIndicator)
{
[activityIndicator stopAnimating];
[activityIndicator removeFromSuperview];
activityIndicator = nil;
}
[self evaluateJavaScript:kMXKJavascriptSendObjectMessage completionHandler:^(id _Nullable response, NSError * _Nullable error) {
}];
[self evaluateJavaScript:kMXKJavascriptOnRegistered completionHandler:^(id _Nullable response, NSError * _Nullable error) {
}];
[self evaluateJavaScript:kMXKJavascriptOnLogin completionHandler:^(id _Nullable response, NSError * _Nullable error) {
}];
}
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
{
MXLogDebug(@"[MXKAuthenticationFallbackWebView] decidePolicyForNavigationAction");
NSString *urlString = navigationAction.request.URL.absoluteString;
if ([urlString hasPrefix:@"js:"])
{
// do not log urlString, it may have an access token
MXLogDebug(@"[MXKAuthenticationFallbackWebView] URL has js: prefix");
// Listen only to scheme of the JS-WKWebView bridge
NSString *jsonString = [[[urlString componentsSeparatedByString:@"js:"] lastObject] stringByReplacingPercentEscapesUsingEncoding:NSASCIIStringEncoding];
NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
NSDictionary *parameters = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers
error:&error];
if (error)
{
MXLogDebug(@"[MXKAuthenticationFallbackWebView] Error when parsing json: %@", error);
}
else
{
if ([@"onRegistered" isEqualToString:parameters[@"action"]])
{
// Translate the JS registration event to MXLoginResponse
// We cannot use [MXLoginResponse modelFromJSON:] because of https://github.com/matrix-org/synapse/issues/4756
// Because of this issue, we cannot get the device_id allocated by the homeserver
// TODO: Fix it once the homeserver issue is fixed (filed at https://github.com/vector-im/riot-meta/issues/273).
MXLoginResponse *loginResponse = [MXLoginResponse new];
loginResponse.homeserver = parameters[@"homeServer"];
loginResponse.userId = parameters[@"userId"];
loginResponse.accessToken = parameters[@"accessToken"];
MXLogDebug(@"[MXKAuthenticationFallbackWebView] Registered on homeserver: %@", loginResponse.homeserver);
// Sanity check
if (loginResponse.homeserver.length && loginResponse.userId.length && loginResponse.accessToken.length)
{
MXLogDebug(@"[MXKAuthenticationFallbackWebView] Call success block");
// And inform the client
onSuccess(loginResponse);
}
}
else if ([@"onLogin" isEqualToString:parameters[@"action"]])
{
// Translate the JS login event to MXLoginResponse
MXLoginResponse *loginResponse;
MXJSONModelSetMXJSONModel(loginResponse, MXLoginResponse, parameters[@"response"]);
MXLogDebug(@"[MXKAuthenticationFallbackWebView] Logged in on homeserver: %@", loginResponse.homeserver);
// Sanity check
if (loginResponse.homeserver.length && loginResponse.userId.length && loginResponse.accessToken.length)
{
MXLogDebug(@"[MXKAuthenticationFallbackWebView] Call success block");
// And inform the client
onSuccess(loginResponse);
}
}
}
decisionHandler(WKNavigationActionPolicyCancel);
return;
}
decisionHandler(WKNavigationActionPolicyAllow);
}
@end
@@ -0,0 +1,33 @@
/*
Copyright 2016 OpenMarket Ltd
Copyright 2018 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 <MatrixSDK/MatrixSDK.h>
#import <WebKit/WebKit.h>
@interface MXKAuthenticationRecaptchaWebView : WKWebView
/**
Open reCAPTCHA widget into a webview.
@param siteKey the site key.
@param homeServer the homeserver URL.
@param callback the block called when the user has received reCAPTCHA response.
*/
- (void)openRecaptchaWidgetWithSiteKey:(NSString*)siteKey fromHomeServer:(NSString*)homeServer callback:(void (^)(NSString *response))callback;
@end
@@ -0,0 +1,126 @@
/*
Copyright 2016 OpenMarket Ltd
Copyright 2018 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 "MXKAuthenticationRecaptchaWebView.h"
NSString *kMXKRecaptchaHTMLString = @"<html> \
<head> \
<meta name='viewport' content='initial-scale=1.0' /> \
<script type=\"text/javascript\"> \
var verifyCallback = function(response) { \
/* Generic method to make a bridge between JS and the WKWebView*/ \
var iframe = document.createElement('iframe'); \
iframe.setAttribute('src', 'js:' + JSON.stringify({'action': 'verifyCallback', 'response': response})); \
\
document.documentElement.appendChild(iframe); \
iframe.parentNode.removeChild(iframe); \
iframe = null; \
}; \
var onloadCallback = function() { \
grecaptcha.render('recaptcha_widget', { \
'sitekey' : '%@', \
'callback': verifyCallback \
}); \
}; \
</script> \
</head> \
<body> \
<div id=\"recaptcha_widget\"></div> \
<script src=\"https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit\" async defer> \
</script> \
</body> \
</html>";
@interface MXKAuthenticationRecaptchaWebView () <WKNavigationDelegate>
{
// The block called when the reCAPTCHA response is received
void (^onResponse)(NSString *);
// Activity indicator
UIActivityIndicatorView *activityIndicator;
}
@end
@implementation MXKAuthenticationRecaptchaWebView
- (void)dealloc
{
if (activityIndicator)
{
[activityIndicator removeFromSuperview];
activityIndicator = nil;
}
}
- (void)openRecaptchaWidgetWithSiteKey:(NSString*)siteKey fromHomeServer:(NSString*)homeServer callback:(void (^)(NSString *response))callback
{
self.navigationDelegate = self;
onResponse = callback;
// Add activity indicator
activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
activityIndicator.center = self.center;
[self addSubview:activityIndicator];
[activityIndicator startAnimating];
NSString *htmlString = [NSString stringWithFormat:kMXKRecaptchaHTMLString, siteKey];
[self loadHTMLString:htmlString baseURL:[NSURL URLWithString:homeServer]];
}
#pragma mark - WKNavigationDelegate
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
{
if (activityIndicator)
{
[activityIndicator stopAnimating];
[activityIndicator removeFromSuperview];
activityIndicator = nil;
}
}
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
{
NSString *urlString = navigationAction.request.URL.absoluteString;
if ([urlString hasPrefix:@"js:"])
{
// Listen only to scheme of the JS-WKWebView bridge
NSString *jsonString = [[[urlString componentsSeparatedByString:@"js:"] lastObject] stringByReplacingPercentEscapesUsingEncoding:NSASCIIStringEncoding];
NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
NSDictionary *parameters = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers
error:&error];
if (!error)
{
if ([@"verifyCallback" isEqualToString:parameters[@"action"]])
{
// Transfer the reCAPTCHA response
onResponse(parameters[@"response"]);
}
}
decisionHandler(WKNavigationActionPolicyCancel);
return;
}
decisionHandler(WKNavigationActionPolicyAllow);
}
@end