session-ios/Signal/src/ViewControllers/AppSettings/OWSLinkDeviceViewController.m

220 lines
10 KiB
Objective-C

//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "OWSLinkDeviceViewController.h"
#import "OWSDeviceProvisioningURLParser.h"
#import "OWSLinkedDevicesTableViewController.h"
#import "Signal-Swift.h"
#import <SignalCoreKit/Cryptography.h>
#import <SignalMessaging/OWSProfileManager.h>
#import <SignalServiceKit/OWSDevice.h>
#import <SignalServiceKit/OWSDeviceProvisioner.h>
#import <SignalServiceKit/OWSIdentityManager.h>
#import <SignalServiceKit/OWSReadReceiptManager.h>
#import <SignalServiceKit/TSAccountManager.h>
NS_ASSUME_NONNULL_BEGIN
@interface OWSLinkDeviceViewController ()
@property (nonatomic) YapDatabaseConnection *dbConnection;
@property (nonatomic) IBOutlet UIView *qrScanningView;
@property (nonatomic) IBOutlet UILabel *scanningInstructionsLabel;
@property (nonatomic) OWSQRCodeScanningViewController *qrScanningController;
@property (nonatomic, readonly) OWSReadReceiptManager *readReceiptManager;
@end
@implementation OWSLinkDeviceViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.dbConnection = [[OWSPrimaryStorage sharedManager] newDatabaseConnection];
// HACK to get full width preview layer
CGRect oldFrame = self.qrScanningView.frame;
self.qrScanningView.frame = CGRectMake(
oldFrame.origin.x, oldFrame.origin.y, self.view.frame.size.width, self.view.frame.size.height / 2.0f - 32.0f);
// END HACK to get full width preview layer
self.scanningInstructionsLabel.text = NSLocalizedString(@"LINK_DEVICE_SCANNING_INSTRUCTIONS",
@"QR Scanning screen instructions, placed alongside a camera view for scanning QR Codes");
self.title
= NSLocalizedString(@"LINK_NEW_DEVICE_TITLE", "Navigation title when scanning QR code to add new device.");
}
- (OWSProfileManager *)profileManager
{
return [OWSProfileManager sharedManager];
}
- (OWSReadReceiptManager *)readReceiptManager
{
return [OWSReadReceiptManager sharedManager];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self.qrScanningController startCapture];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(nullable id)sender
{
if ([segue.identifier isEqualToString:@"embedDeviceQRScanner"]) {
OWSQRCodeScanningViewController *qrScanningController
= (OWSQRCodeScanningViewController *)segue.destinationViewController;
qrScanningController.scanDelegate = self;
self.qrScanningController = qrScanningController;
}
}
// pragma mark - OWSQRScannerDelegate
- (void)controller:(OWSQRCodeScanningViewController *)controller didDetectQRCodeWithString:(NSString *)string
{
OWSDeviceProvisioningURLParser *parser = [[OWSDeviceProvisioningURLParser alloc] initWithProvisioningURL:string];
if (!parser.isValid) {
OWSLogError(@"Unable to parse provisioning params from QRCode: %@", string);
NSString *title = NSLocalizedString(@"LINK_DEVICE_INVALID_CODE_TITLE", @"report an invalid linking code");
NSString *body = NSLocalizedString(@"LINK_DEVICE_INVALID_CODE_BODY", @"report an invalid linking code");
UIAlertController *alertController =
[UIAlertController alertControllerWithTitle:title message:body preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *cancelAction =
[UIAlertAction actionWithTitle:CommonStrings.cancelButton
style:UIAlertActionStyleCancel
handler:^(UIAlertAction *action) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.navigationController popViewControllerAnimated:YES];
});
}];
[alertController addAction:cancelAction];
UIAlertAction *proceedAction =
[UIAlertAction actionWithTitle:NSLocalizedString(@"LINK_DEVICE_RESTART", @"attempt another linking")
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
[self.qrScanningController startCapture];
}];
[alertController addAction:proceedAction];
[self presentViewController:alertController animated:YES completion:nil];
} else {
NSString *title = NSLocalizedString(
@"LINK_DEVICE_PERMISSION_ALERT_TITLE", @"confirm the users intent to link a new device");
NSString *linkingDescription
= NSLocalizedString(@"LINK_DEVICE_PERMISSION_ALERT_BODY", @"confirm the users intent to link a new device");
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title
message:linkingDescription
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *cancelAction =
[UIAlertAction actionWithTitle:CommonStrings.cancelButton
style:UIAlertActionStyleCancel
handler:^(UIAlertAction *action) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.navigationController popViewControllerAnimated:YES];
});
}];
[alertController addAction:cancelAction];
UIAlertAction *proceedAction =
[UIAlertAction actionWithTitle:NSLocalizedString(@"CONFIRM_LINK_NEW_DEVICE_ACTION", @"Button text")
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
[self provisionWithParser:parser];
}];
[alertController addAction:proceedAction];
[self presentViewController:alertController animated:YES completion:nil];
}
}
- (void)provisionWithParser:(OWSDeviceProvisioningURLParser *)parser
{
// Optimistically set this flag.
[OWSDeviceManager.sharedManager setMayHaveLinkedDevices];
ECKeyPair *_Nullable identityKeyPair = [[OWSIdentityManager sharedManager] identityKeyPair];
OWSAssertDebug(identityKeyPair);
NSData *myPublicKey = identityKeyPair.publicKey;
NSData *myPrivateKey = identityKeyPair.privateKey;
NSString *accountIdentifier = [TSAccountManager localNumber];
NSData *myProfileKeyData = self.profileManager.localProfileKey.keyData;
BOOL areReadReceiptsEnabled = self.readReceiptManager.areReadReceiptsEnabled;
OWSDeviceProvisioner *provisioner = [[OWSDeviceProvisioner alloc] initWithMyPublicKey:myPublicKey
myPrivateKey:myPrivateKey
theirPublicKey:parser.publicKey
theirEphemeralDeviceId:parser.ephemeralDeviceId
accountIdentifier:accountIdentifier
profileKey:myProfileKeyData
readReceiptsEnabled:areReadReceiptsEnabled];
[provisioner
provisionWithSuccess:^{
OWSLogInfo(@"Successfully provisioned device.");
dispatch_async(dispatch_get_main_queue(), ^{
[self.linkedDevicesTableViewController expectMoreDevices];
[self.navigationController popToViewController:self.linkedDevicesTableViewController animated:YES];
// The service implementation of the socket connection caches the linked device state,
// so all sync message sends will fail on the socket until it is cycled.
[TSSocketManager.sharedManager cycleSocket];
});
}
failure:^(NSError *error) {
OWSLogError(@"Failed to provision device with error: %@", error);
dispatch_async(dispatch_get_main_queue(), ^{
[self presentViewController:[self retryAlertControllerWithError:error
retryBlock:^{
[self provisionWithParser:parser];
}]
animated:YES
completion:nil];
});
}];
}
- (UIAlertController *)retryAlertControllerWithError:(NSError *)error retryBlock:(void (^)(void))retryBlock
{
NSString *title = NSLocalizedString(@"LINKING_DEVICE_FAILED_TITLE", @"Alert Title");
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title
message:error.localizedDescription
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *retryAction = [UIAlertAction actionWithTitle:[CommonStrings retryButton]
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
retryBlock();
}];
[alertController addAction:retryAction];
UIAlertAction *cancelAction =
[UIAlertAction actionWithTitle:CommonStrings.cancelButton
style:UIAlertActionStyleCancel
handler:^(UIAlertAction *action) {
dispatch_async(dispatch_get_main_queue(), ^{
[self dismissViewControllerAnimated:YES completion:nil];
});
}];
[alertController addAction:cancelAction];
return alertController;
}
@end
NS_ASSUME_NONNULL_END