2019-12-16 20:16:22 +03:00
/ *
2020-01-24 19:16:46 +03:00
Copyright 2019 , 2020 The Matrix . org Foundation C . I . C .
2019-12-16 20:16:22 +03:00
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 .
* /
2020-01-29 10:53:45 +03:00
import React from "react" ;
import PropTypes from "prop-types" ;
2020-01-24 19:16:46 +03:00
2020-01-17 17:50:27 +03:00
import * as sdk from '../../../index' ;
2020-01-16 23:23:32 +03:00
import { verificationMethods } from 'matrix-js-sdk/src/crypto' ;
2020-02-14 15:48:18 +03:00
import { SCAN _QR _CODE _METHOD } from "matrix-js-sdk/src/crypto/verification/QRCode" ;
2020-01-18 05:53:33 +03:00
import VerificationQRCode from "../elements/crypto/VerificationQRCode" ;
2020-01-24 19:16:46 +03:00
import { _t } from "../../../languageHandler" ;
import E2EIcon from "../rooms/E2EIcon" ;
2020-01-28 14:13:09 +03:00
import {
2020-01-29 10:53:45 +03:00
PHASE _UNSENT ,
PHASE _REQUESTED ,
2020-01-28 14:13:09 +03:00
PHASE _READY ,
PHASE _DONE ,
PHASE _STARTED ,
2020-01-30 19:55:43 +03:00
PHASE _CANCELLED , VerificationRequest ,
2020-01-28 14:13:09 +03:00
} from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest" ;
import Spinner from "../elements/Spinner" ;
2019-12-16 20:16:22 +03:00
export default class VerificationPanel extends React . PureComponent {
2020-01-29 10:53:45 +03:00
static propTypes = {
2020-01-31 18:04:44 +03:00
layout : PropTypes . string ,
2020-01-29 10:53:45 +03:00
request : PropTypes . object . isRequired ,
member : PropTypes . object . isRequired ,
phase : PropTypes . oneOf ( [
PHASE _UNSENT ,
PHASE _REQUESTED ,
PHASE _READY ,
PHASE _STARTED ,
PHASE _CANCELLED ,
PHASE _DONE ,
] ) . isRequired ,
onClose : PropTypes . func . isRequired ,
} ;
2019-12-16 20:16:22 +03:00
constructor ( props ) {
super ( props ) ;
2020-01-30 19:55:43 +03:00
this . state = {
qrCodeProps : null , // generated by the VerificationQRCode component itself
} ;
2020-01-29 19:58:23 +03:00
this . _hasVerifier = false ;
2020-02-14 15:48:18 +03:00
if ( this . props . request . otherPartySupportsMethod ( SCAN _QR _CODE _METHOD ) ) {
this . _generateQRCodeProps ( props . request ) ;
}
2020-01-30 19:55:43 +03:00
}
async _generateQRCodeProps ( verificationRequest : VerificationRequest ) {
try {
this . setState ( { qrCodeProps : await VerificationQRCode . getPropsForRequest ( verificationRequest ) } ) ;
} catch ( e ) {
console . error ( e ) ;
// Do nothing - we won't render a QR code.
}
2019-12-16 20:16:22 +03:00
}
2020-01-28 14:13:09 +03:00
renderQRPhase ( pending ) {
2020-02-14 15:48:18 +03:00
const { member , request } = this . props ;
2020-03-20 15:37:00 +03:00
const showSAS = request . otherPartySupportsMethod ( verificationMethods . SAS ) ;
const showQR = request . otherPartySupportsMethod ( SCAN _QR _CODE _METHOD ) ;
2020-01-24 19:16:46 +03:00
const AccessibleButton = sdk . getComponent ( 'elements.AccessibleButton' ) ;
2020-01-24 19:41:43 +03:00
2020-02-14 15:48:18 +03:00
const noCommonMethodError = ! showSAS && ! showQR ?
< p > { _t ( "The session you are trying to verify doesn't support scanning a QR code or emoji verification, which is what Riot supports. Try with a different client." ) } < / p > :
null ;
2020-01-31 18:04:44 +03:00
if ( this . props . layout === 'dialog' ) {
// HACK: This is a terrible idea.
2020-02-14 15:48:18 +03:00
let qrBlock ;
let sasBlock ;
if ( showQR ) {
let qrCode ;
if ( this . state . qrCodeProps ) {
qrCode = < VerificationQRCode { ... this . state . qrCodeProps } / > ;
} else {
qrCode = < div className = 'mx_VerificationPanel_QRPhase_noQR' > < Spinner / > < / d i v > ;
}
qrBlock =
< div className = 'mx_VerificationPanel_QRPhase_startOption' >
< p > { _t ( "Scan this unique code" ) } < / p >
{ qrCode }
< / d i v > ;
2020-01-31 18:04:44 +03:00
}
2020-02-14 15:48:18 +03:00
if ( showSAS ) {
sasBlock =
< div className = 'mx_VerificationPanel_QRPhase_startOption' >
< p > { _t ( "Compare unique emoji" ) } < / p >
< span className = 'mx_VerificationPanel_QRPhase_helpText' > { _t ( "Compare a unique set of emoji if you don't have a camera on either device" ) } < / s p a n >
< AccessibleButton disabled = { this . state . emojiButtonClicked } onClick = { this . _startSAS } kind = 'primary' >
{ _t ( "Start" ) }
< / A c c e s s i b l e B u t t o n >
< / d i v > ;
}
const or = qrBlock && sasBlock ?
< div className = 'mx_VerificationPanel_QRPhase_betweenText' > { _t ( "or" ) } < / d i v > : n u l l ;
2020-01-31 18:04:44 +03:00
return (
< div >
{ _t ( "Verify this session by completing one of the following:" ) }
2020-02-10 16:20:54 +03:00
< div className = 'mx_VerificationPanel_QRPhase_startOptions' >
2020-02-14 15:48:18 +03:00
{ qrBlock }
{ or }
{ sasBlock }
{ noCommonMethodError }
2020-01-31 18:04:44 +03:00
< / d i v >
< / d i v >
) ;
}
2020-02-14 15:48:18 +03:00
let qrBlock ;
if ( this . state . qrCodeProps ) {
qrBlock = < div className = "mx_UserInfo_container" >
2020-02-10 16:44:20 +03:00
< h3 > { _t ( "Verify by scanning" ) } < / h 3 >
2020-01-27 20:17:05 +03:00
< p > { _t ( "Ask %(displayName)s to scan your code:" , {
2020-01-24 19:16:46 +03:00
displayName : member . displayName || member . name || member . userId ,
} ) } < / p >
2020-01-24 19:41:43 +03:00
2020-01-27 20:17:05 +03:00
< div className = "mx_VerificationPanel_qrCode" >
2020-01-30 19:55:43 +03:00
< VerificationQRCode { ... this . state . qrCodeProps } / >
2020-01-27 20:17:05 +03:00
< / d i v >
2020-02-14 15:48:18 +03:00
< / d i v > ;
}
2020-01-24 19:16:46 +03:00
2020-02-14 15:48:18 +03:00
let sasBlock ;
if ( showSAS ) {
let button ;
if ( pending ) {
button = < Spinner / > ;
} else {
const disabled = this . state . emojiButtonClicked ;
button = (
< AccessibleButton disabled = { disabled } kind = "primary" className = "mx_UserInfo_wideButton" onClick = { this . _startSAS } >
{ _t ( "Verify by emoji" ) }
< / A c c e s s i b l e B u t t o n >
) ;
}
const sasLabel = this . state . qrCodeProps ?
_t ( "If you can't scan the code above, verify by comparing unique emoji." ) :
_t ( "Verify by comparing unique emoji." ) ;
sasBlock = < div className = "mx_UserInfo_container" >
2020-02-10 16:44:20 +03:00
< h3 > { _t ( "Verify by emoji" ) } < / h 3 >
2020-02-14 15:48:18 +03:00
< p > { sasLabel } < / p >
2020-01-28 14:13:09 +03:00
{ button }
2020-02-14 15:48:18 +03:00
< / d i v > ;
}
const noCommonMethodBlock = noCommonMethodError ?
< div className = "mx_UserInfo_container" > { noCommonMethodError } < / d i v > :
null ;
// TODO: add way to open camera to scan a QR code
return < React . Fragment >
{ qrBlock }
{ sasBlock }
{ noCommonMethodBlock }
2020-01-24 19:16:46 +03:00
< / R e a c t . F r a g m e n t > ;
2019-12-20 23:30:47 +03:00
}
2020-01-24 19:16:46 +03:00
renderVerifiedPhase ( ) {
const { member } = this . props ;
2019-12-16 20:16:22 +03:00
const AccessibleButton = sdk . getComponent ( 'elements.AccessibleButton' ) ;
2020-01-24 19:16:46 +03:00
return (
< div className = "mx_UserInfo_container mx_VerificationPanel_verified_section" >
< h3 > Verified < / h 3 >
< p > { _t ( "You've successfully verified %(displayName)s!" , {
displayName : member . displayName || member . name || member . userId ,
} ) } < / p >
2020-02-13 17:15:08 +03:00
< E2EIcon isUser = { true } status = "verified" size = { 128 } hideTooltip = { true } / >
2020-01-24 19:16:46 +03:00
< p > Verify all users in a room to ensure it ' s secure . < / p >
2020-01-28 14:13:09 +03:00
2020-01-29 17:11:50 +03:00
< AccessibleButton kind = "primary" className = "mx_UserInfo_wideButton" onClick = { this . props . onClose } >
2020-01-24 19:16:46 +03:00
{ _t ( "Got it" ) }
< / A c c e s s i b l e B u t t o n >
< / d i v >
) ;
}
2020-01-28 14:13:09 +03:00
renderCancelledPhase ( ) {
2020-01-24 19:16:46 +03:00
const { member , request } = this . props ;
2020-01-28 14:13:09 +03:00
const AccessibleButton = sdk . getComponent ( 'elements.AccessibleButton' ) ;
let text ;
if ( request . cancellationCode === "m.timeout" ) {
text = _t ( "Verification timed out. Start verification again from their profile." ) ;
} else if ( request . cancellingUserId === request . otherUserId ) {
text = _t ( "%(displayName)s cancelled verification. Start verification again from their profile." , {
displayName : member . displayName || member . name || member . userId ,
} ) ;
} else {
text = _t ( "You cancelled verification. Start verification again from their profile." ) ;
}
return (
< div className = "mx_UserInfo_container" >
< h3 > Verification cancelled < / h 3 >
< p > { text } < / p >
2020-01-29 17:11:50 +03:00
< AccessibleButton kind = "primary" className = "mx_UserInfo_wideButton" onClick = { this . props . onClose } >
2020-01-28 14:13:09 +03:00
{ _t ( "Got it" ) }
< / A c c e s s i b l e B u t t o n >
< / d i v >
) ;
}
render ( ) {
2020-01-29 11:00:32 +03:00
const { member , phase } = this . props ;
2020-01-28 14:13:09 +03:00
2020-01-24 19:16:46 +03:00
const displayName = member . displayName || member . name || member . userId ;
2019-12-16 20:16:22 +03:00
2020-01-29 11:00:32 +03:00
switch ( phase ) {
2020-01-28 14:13:09 +03:00
case PHASE _READY :
return this . renderQRPhase ( ) ;
case PHASE _STARTED :
if ( this . state . sasEvent ) {
const VerificationShowSas = sdk . getComponent ( 'views.verification.VerificationShowSas' ) ;
return < div className = "mx_UserInfo_container" >
< h3 > Compare emoji < / h 3 >
< VerificationShowSas
displayName = { displayName }
sas = { this . state . sasEvent . sas }
onCancel = { this . _onSasMismatchesClick }
onDone = { this . _onSasMatchesClick }
/ >
< / d i v > ;
} else {
return this . renderQRPhase ( true ) ; // keep showing same phase but with a spinner
}
case PHASE _DONE :
return this . renderVerifiedPhase ( ) ;
case PHASE _CANCELLED :
return this . renderCancelledPhase ( ) ;
2019-12-16 20:16:22 +03:00
}
2020-01-29 11:08:52 +03:00
console . error ( "VerificationPanel unhandled phase:" , phase ) ;
2020-01-24 19:16:46 +03:00
return null ;
2019-12-16 20:16:22 +03:00
}
_startSAS = async ( ) => {
2020-02-02 12:48:02 +03:00
this . setState ( { emojiButtonClicked : true } ) ;
2019-12-16 20:16:22 +03:00
const verifier = this . props . request . beginKeyVerification ( verificationMethods . SAS ) ;
try {
2019-12-18 20:26:54 +03:00
await verifier . verify ( ) ;
2019-12-19 19:08:53 +03:00
} catch ( err ) {
console . error ( err ) ;
2019-12-16 20:16:22 +03:00
}
} ;
_onSasMatchesClick = ( ) => {
this . state . sasEvent . confirm ( ) ;
} ;
_onSasMismatchesClick = ( ) => {
2020-01-28 14:13:09 +03:00
this . state . sasEvent . mismatch ( ) ;
2019-12-16 20:16:22 +03:00
} ;
_onVerifierShowSas = ( sasEvent ) => {
2020-02-24 15:48:41 +03:00
const { request } = this . props ;
request . verifier . off ( 'show_sas' , this . _onVerifierShowSas ) ;
2019-12-16 20:16:22 +03:00
this . setState ( { sasEvent } ) ;
} ;
2019-12-19 19:28:23 +03:00
_onRequestChange = async ( ) => {
2019-12-18 20:26:54 +03:00
const { request } = this . props ;
2020-01-29 17:23:13 +03:00
const hadVerifier = this . _hasVerifier ;
this . _hasVerifier = ! ! request . verifier ;
if ( ! hadVerifier && this . _hasVerifier ) {
2020-02-24 15:48:41 +03:00
request . verifier . on ( 'show_sas' , this . _onVerifierShowSas ) ;
2019-12-18 20:26:54 +03:00
try {
2019-12-19 19:28:23 +03:00
// on the requester side, this is also awaited in _startSAS,
// but that's ok as verify should return the same promise.
await request . verifier . verify ( ) ;
2019-12-18 20:26:54 +03:00
} catch ( err ) {
console . error ( "error verify" , err ) ;
}
}
2019-12-16 20:16:22 +03:00
} ;
componentDidMount ( ) {
2020-02-13 16:32:33 +03:00
const { request } = this . props ;
request . on ( "change" , this . _onRequestChange ) ;
if ( request . verifier ) {
this . setState ( { sasEvent : request . verifier . sasEvent } ) ;
}
2020-01-29 19:58:23 +03:00
this . _onRequestChange ( ) ;
2019-12-16 20:16:22 +03:00
}
componentWillUnmount ( ) {
2020-02-24 15:48:41 +03:00
const { request } = this . props ;
if ( request . verifier ) {
request . verifier . off ( 'show_sas' , this . _onVerifierShowSas ) ;
}
request . off ( "change" , this . _onRequestChange ) ;
2019-12-16 20:16:22 +03:00
}
}