2019-09-20 07:53:24 +02:00
import NVActivityIndicatorView
@objc ( LKDeviceLinkingModal )
2019-09-24 03:18:14 +02:00
final class DeviceLinkingModal : Modal , DeviceLinkingSessionDelegate {
2019-09-24 03:59:17 +02:00
private let mode : Mode
private let delegate : DeviceLinkingModalDelegate ?
2019-09-24 03:18:14 +02:00
private var deviceLink : DeviceLink ?
2019-09-20 07:53:24 +02:00
2019-09-24 03:59:17 +02:00
// MARK: T y p e s
enum Mode : String { case master , slave }
2019-09-20 07:53:24 +02:00
// MARK: C o m p o n e n t s
2019-09-23 06:20:03 +02:00
private lazy var topSpacer = UIView . spacer ( withHeight : 8 )
2019-09-20 07:53:24 +02:00
private lazy var spinner = NVActivityIndicatorView ( frame : CGRect . zero , type : . circleStrokeSpin , color : . white , padding : nil )
2019-11-25 00:20:46 +01:00
private lazy var qrCodeImageView : UIImageView = {
let result = UIImageView ( )
result . contentMode = . scaleAspectFit
return result
} ( )
2019-09-20 07:53:24 +02:00
private lazy var titleLabel : UILabel = {
let result = UILabel ( )
result . textColor = Theme . primaryColor
result . font = UIFont . ows_dynamicTypeHeadlineClamped
result . numberOfLines = 0
result . lineBreakMode = . byWordWrapping
result . textAlignment = . center
return result
} ( )
private lazy var subtitleLabel : UILabel = {
let result = UILabel ( )
result . textColor = Theme . primaryColor
result . font = UIFont . ows_dynamicTypeCaption1Clamped
result . numberOfLines = 0
result . lineBreakMode = . byWordWrapping
result . textAlignment = . center
return result
} ( )
2019-09-23 06:20:03 +02:00
private lazy var mnemonicLabel : UILabel = {
let result = UILabel ( )
result . textColor = Theme . primaryColor
result . font = UIFont . ows_dynamicTypeCaption1Clamped
result . numberOfLines = 0
result . lineBreakMode = . byWordWrapping
result . textAlignment = . center
return result
} ( )
2019-09-26 02:05:00 +02:00
private lazy var buttonStackView : UIStackView = {
let result = UIStackView ( arrangedSubviews : [ authorizeButton , cancelButton ] )
result . axis = . horizontal
result . distribution = . fillEqually
return result
} ( )
2019-09-23 06:20:03 +02:00
private lazy var authorizeButton : OWSFlatButton = {
let result = OWSFlatButton . button ( title : NSLocalizedString ( " Authorize " , comment : " " ) , font : . ows_dynamicTypeBodyClamped , titleColor : . white , backgroundColor : . clear , target : self , selector : #selector ( authorizeDeviceLink ) )
result . setBackgroundColors ( upColor : . clear , downColor : . clear )
return result
} ( )
2019-09-26 02:05:00 +02:00
private lazy var bottomSpacer = UIView . spacer ( withHeight : 8 )
2019-09-23 06:20:03 +02:00
2019-09-20 07:53:24 +02:00
// MARK: L i f e c y c l e
2019-09-24 03:59:17 +02:00
init ( mode : Mode , delegate : DeviceLinkingModalDelegate ? ) {
self . mode = mode
if mode = = . slave {
guard delegate != nil else { preconditionFailure ( " Missing delegate for device linking modal in slave mode. " ) }
}
self . delegate = delegate
super . init ( nibName : nil , bundle : nil )
}
2019-09-24 07:55:03 +02:00
@objc ( initWithMode : delegate : )
convenience init ( modeAsString : String , delegate : DeviceLinkingModalDelegate ? ) {
2019-09-24 03:59:17 +02:00
guard let mode = Mode ( rawValue : modeAsString ) else { preconditionFailure ( " Invalid mode: \( modeAsString ) . " ) }
self . init ( mode : mode , delegate : delegate )
}
required init ? ( coder : NSCoder ) { preconditionFailure ( ) }
override init ( nibName : String ? , bundle : Bundle ? ) { preconditionFailure ( ) }
2019-09-20 07:53:24 +02:00
override func viewDidLoad ( ) {
super . viewDidLoad ( )
2019-09-24 07:05:59 +02:00
switch mode {
case . master : let _ = DeviceLinkingSession . startListeningForLinkingRequests ( with : self )
case . slave : let _ = DeviceLinkingSession . startListeningForLinkingAuthorization ( with : self )
}
2019-09-20 07:53:24 +02:00
}
2019-09-24 02:57:32 +02:00
override func populateContentView ( ) {
2019-11-25 00:20:46 +01:00
let stackView = UIStackView ( arrangedSubviews : [ topSpacer , titleLabel , subtitleLabel , mnemonicLabel , buttonStackView , bottomSpacer ] )
switch mode {
case . master :
stackView . insertArrangedSubview ( qrCodeImageView , at : 1 )
stackView . insertArrangedSubview ( UIView . spacer ( withHeight : 2 ) , at : 2 )
case . slave :
stackView . insertArrangedSubview ( spinner , at : 1 )
stackView . insertArrangedSubview ( UIView . spacer ( withHeight : 8 ) , at : 2 )
}
2019-09-23 06:20:03 +02:00
contentView . addSubview ( stackView )
2019-09-24 02:57:32 +02:00
stackView . spacing = 16
2019-09-23 06:20:03 +02:00
stackView . axis = . vertical
2019-11-25 00:20:46 +01:00
switch mode {
case . master :
qrCodeImageView . set ( . height , to : 128 )
let hexEncodedPublicKey = OWSIdentityManager . shared ( ) . identityKeyPair ( ) ! . hexEncodedPublicKey
let data = hexEncodedPublicKey . data ( using : . utf8 )
let filter = CIFilter ( name : " CIQRCodeGenerator " ) !
filter . setValue ( data , forKey : " inputMessage " )
let qrCodeAsCIImage = filter . outputImage !
let scaledQRCodeAsCIImage = qrCodeAsCIImage . transformed ( by : CGAffineTransform ( scaleX : 4.8 , y : 4.8 ) )
let qrCode = UIImage ( ciImage : scaledQRCodeAsCIImage )
qrCodeImageView . image = qrCode
case . slave :
spinner . set ( . height , to : 64 )
spinner . startAnimating ( )
}
2019-09-24 03:59:17 +02:00
titleLabel . text = {
switch mode {
case . master : return NSLocalizedString ( " Waiting for Device " , comment : " " )
case . slave : return NSLocalizedString ( " Waiting for Authorization " , comment : " " )
}
} ( )
subtitleLabel . text = {
switch mode {
2019-09-25 04:22:34 +02:00
case . master : return NSLocalizedString ( " Create a new account on your other device and click \" Link Device \" when you're at the \" Create Your Loki Messenger Account \" step to start the linking process " , comment : " " )
case . slave : return NSLocalizedString ( " Please check that the words below match the ones shown on your other device " , comment : " " )
2019-09-24 03:59:17 +02:00
}
} ( )
mnemonicLabel . isHidden = ( mode = = . master )
2019-09-25 01:15:23 +02:00
if mode = = . slave {
2019-09-25 04:22:34 +02:00
let hexEncodedPublicKey = OWSIdentityManager . shared ( ) . identityKeyPair ( ) ! . hexEncodedPublicKey . removing05PrefixIfNeeded ( )
2019-11-22 04:22:43 +01:00
mnemonicLabel . text = Mnemonic . hash ( hexEncodedString : hexEncodedPublicKey )
2019-09-25 01:15:23 +02:00
}
2019-12-05 01:42:31 +01:00
let buttonHeight = cancelButton . titleLabel ! . font . pointSize * 48 / 17
2019-09-23 06:20:03 +02:00
authorizeButton . set ( . height , to : buttonHeight )
cancelButton . set ( . height , to : buttonHeight )
authorizeButton . isHidden = true
2019-09-26 02:05:00 +02:00
bottomSpacer . isHidden = true
2019-09-24 02:57:32 +02:00
stackView . pin ( . leading , to : . leading , of : contentView , withInset : 16 )
stackView . pin ( . top , to : . top , of : contentView , withInset : 16 )
contentView . pin ( . trailing , to : . trailing , of : stackView , withInset : 16 )
contentView . pin ( . bottom , to : . bottom , of : stackView , withInset : 16 )
2019-09-20 07:53:24 +02:00
}
// MARK: D e v i c e L i n k i n g
2019-09-24 03:18:14 +02:00
func requestUserAuthorization ( for deviceLink : DeviceLink ) {
2019-09-23 06:20:03 +02:00
self . deviceLink = deviceLink
2019-09-25 01:15:23 +02:00
topSpacer . isHidden = true
2019-11-25 00:20:46 +01:00
qrCodeImageView . isHidden = true
2019-09-25 01:15:23 +02:00
titleLabel . text = NSLocalizedString ( " Linking Request Received " , comment : " " )
2019-09-25 04:22:34 +02:00
subtitleLabel . text = NSLocalizedString ( " Please check that the words below match the ones shown on your other device " , comment : " " )
let hexEncodedPublicKey = deviceLink . slave . hexEncodedPublicKey . removing05PrefixIfNeeded ( )
2019-11-22 04:22:43 +01:00
mnemonicLabel . text = Mnemonic . hash ( hexEncodedString : hexEncodedPublicKey )
2019-09-25 01:15:23 +02:00
mnemonicLabel . isHidden = false
authorizeButton . isHidden = false
2019-09-20 07:53:24 +02:00
}
2019-09-23 06:20:03 +02:00
@objc private func authorizeDeviceLink ( ) {
let deviceLink = self . deviceLink !
2019-09-24 07:55:03 +02:00
let linkingAuthorizationMessage = DeviceLinkingUtilities . getLinkingAuthorizationMessage ( for : deviceLink )
2019-11-08 04:41:06 +01:00
ThreadUtil . enqueue ( linkingAuthorizationMessage )
2019-11-27 06:26:15 +01:00
SSKEnvironment . shared . messageSender . send ( linkingAuthorizationMessage , success : {
let _ = SSKEnvironment . shared . syncManager . syncAllContacts ( )
} ) { _ in
print ( " [Loki] Failed to send device link authorization message. " )
}
2019-09-24 03:18:14 +02:00
let session = DeviceLinkingSession . current !
2019-09-23 06:20:03 +02:00
session . stopListeningForLinkingRequests ( )
2019-09-25 04:22:34 +02:00
session . markLinkingRequestAsProcessed ( )
2019-09-23 06:20:03 +02:00
dismiss ( animated : true , completion : nil )
2019-09-26 08:23:59 +02:00
let master = DeviceLink . Device ( hexEncodedPublicKey : deviceLink . master . hexEncodedPublicKey , signature : linkingAuthorizationMessage . masterSignature )
let signedDeviceLink = DeviceLink ( between : master , and : deviceLink . slave )
2019-11-21 05:55:18 +01:00
LokiStorageAPI . addDeviceLink ( signedDeviceLink ) . done {
self . delegate ? . handleDeviceLinkAuthorized ( signedDeviceLink ) // I n t e n t i o n a l l y c a p t u r e s e l f s t r o n g l y
2019-11-20 02:06:41 +01:00
} . catch { error in
2019-09-26 06:43:37 +02:00
print ( " [Loki] Failed to add device link due to error: \( error ) . " )
}
2019-09-23 06:20:03 +02:00
}
2019-09-24 07:05:59 +02:00
func handleDeviceLinkAuthorized ( _ deviceLink : DeviceLink ) {
2019-09-25 04:22:34 +02:00
let session = DeviceLinkingSession . current !
session . stopListeningForLinkingAuthorization ( )
2019-09-26 02:05:00 +02:00
topSpacer . isHidden = true
spinner . stopAnimating ( )
spinner . isHidden = true
titleLabel . text = NSLocalizedString ( " Device Link Authorized " , comment : " " )
subtitleLabel . text = NSLocalizedString ( " Your device has been linked successfully " , comment : " " )
mnemonicLabel . isHidden = true
buttonStackView . isHidden = true
bottomSpacer . isHidden = false
2019-09-26 06:43:37 +02:00
LokiStorageAPI . addDeviceLink ( deviceLink ) . catch { error in
print ( " [Loki] Failed to add device link due to error: \( error ) . " )
}
2019-09-26 02:05:00 +02:00
Timer . scheduledTimer ( withTimeInterval : 2 , repeats : false ) { _ in
self . delegate ? . handleDeviceLinkAuthorized ( deviceLink )
self . dismiss ( animated : true , completion : nil )
}
2019-09-25 04:22:34 +02:00
}
@objc override func cancel ( ) {
2019-10-08 03:29:40 +02:00
guard let session = DeviceLinkingSession . current else {
return print ( " [Loki] Device linking session missing. " ) // S h o u l d n e v e r o c c u r
}
session . stopListeningForLinkingRequests ( )
session . markLinkingRequestAsProcessed ( ) // O n l y r e l e v a n t i n m a s t e r m o d e
2019-09-25 04:22:34 +02:00
delegate ? . handleDeviceLinkingModalDismissed ( ) // O n l y r e l e v a n t i n s l a v e m o d e
2019-10-01 07:55:48 +02:00
if let deviceLink = deviceLink {
OWSPrimaryStorage . shared ( ) . dbReadWriteConnection . readWrite { transaction in
OWSPrimaryStorage . shared ( ) . removePreKeyBundle ( forContact : deviceLink . slave . hexEncodedPublicKey , transaction : transaction )
}
}
2019-09-25 04:22:34 +02:00
dismiss ( animated : true , completion : nil )
2019-09-24 07:05:59 +02:00
}
2019-09-20 07:53:24 +02:00
}