Unverified Commit 66d938a1 authored by Hanwen Cheng's avatar Hanwen Cheng Committed by GitHub
Browse files

feat: simplify sign flow (#603)

* fix: upgrade react navigation v5

* update project settings in ios

* improve navigation by upgrading to react navigation v5

* make compiler happy

* unlink react-native-secure-storage

* make detox and react-navigation happy again

* make e2e test happy

* use safe area context app wide

* delete stray comment

* fix screen heading styles

* fix pin backup navigation

* revert change to rust target

* fix ui overlap on android

* remove bounce in ios scroll view

* lint fix

* feat: enable passworded identity

* use keyboard scroll view

* complete password generation

* update react-native-camera and related polkadot api packages

* add registry store and use type override

* reduce android bundle size

* update yarn.lock

* update metadata

* prettier happy

* update polkadot api

* add password in pinInputt

* remove password from identity

* add password in path derivation

* remove log

* complete password support

* make compiler happy

* refactor account store error handling

* remove password check when signing

* add lock icon for passworded account

* add hint also on path

* add extra hint text

* fix autofocus and remove useRef

* add e2e test suit for passworded account

* make lint happy

* destroy reference when app go into background

* fix lint

* add seed ref functions

* enable pin los address creation

* signing with seed reference

* fix bridge in ios

* use lists for data pointers for each identity

* createSeedRefWithNewSeed function

* fix logic error

* more fix and complete e2e test

* remove console log

* add copyright

* fix lint errors

* refactor qrscanner to functional componnet

* move files into module

* finish sign process during scanning

* refactor signed tx and message page

* simplify seed ref getter in scanning

* stash changes

* refactor scanner store and improve multi part sign

* complete signing for multi part

* fix e2e

* make lint happy

* update babel related packages

* merge master

* Update yarn.lock

* prevent detox to be included into package.
parent fbc70c01
Pipeline #91060 failed with stages
in 3 minutes and 55 seconds
module.exports = {
ignore: ['node_modules/detox'],
plugins: [
[
'rewrite-require',
......
......@@ -44,12 +44,12 @@
}
},
"dependencies": {
"@polkadot/api": "1.10.0-beta.3",
"@polkadot/reactnative-identicon": "0.52.0-beta.24",
"@polkadot/types": "1.10.0-beta.3",
"@polkadot/types-known": "1.10.0-beta.3",
"@polkadot/util": "2.8.0-beta.2",
"@polkadot/util-crypto": "2.8.0-beta.2",
"@polkadot/api": "1.11.2",
"@polkadot/reactnative-identicon": "0.52.1",
"@polkadot/types": "1.11.2",
"@polkadot/types-known": "1.11.2",
"@polkadot/util": "2.8.1",
"@polkadot/util-crypto": "2.8.1",
"@react-native-community/masked-view": "^0.1.6",
"@react-native-community/netinfo": "^4.1.5",
"@react-navigation/native": "^5.0.10",
......@@ -76,13 +76,13 @@
"vm-browserify": "1.1.0"
},
"devDependencies": {
"@babel/cli": "^7.5.0",
"@babel/core": "^7.5.4",
"@babel/plugin-proposal-class-properties": "^7.5.0",
"@babel/plugin-transform-runtime": "^7.5.0",
"@babel/cli": "^7.8.4",
"@babel/core": "^7.9.6",
"@babel/plugin-proposal-class-properties": "^7.8.3",
"@babel/plugin-transform-runtime": "^7.9.6",
"@babel/preset-typescript": "^7.9.0",
"@babel/runtime": "^7.5.4",
"@react-native-community/eslint-config": "^0.0.5",
"@babel/runtime": "^7.9.6",
"@react-native-community/eslint-config": "^1.1.0",
"@types/detox": "^14.5.2",
"@types/jasmine": "^3.5.5",
"@types/jest": "^25.1.3",
......@@ -91,11 +91,11 @@
"@typescript-eslint/eslint-plugin": "^2.26.0",
"@typescript-eslint/parser": "^2.26.0",
"babel-eslint": "10.1.0",
"babel-jest": "^25.1.0",
"babel-jest": "^25.5.1",
"babel-plugin-module-resolver": "^4.0.0",
"babel-plugin-rewrite-require": "^1.14.5",
"babel-plugin-tester": "^8.0.1",
"detox": "^16.2.0",
"babel-plugin-tester": "^9.0.1",
"detox": "^16.4.0",
"eslint": "^6.8.0",
"eslint-config-prettier": "^6.10.1",
"eslint-import-resolver-typescript": "^2.0.0",
......@@ -105,7 +105,7 @@
"husky": "^3.0.8",
"jest": "^25.1.0",
"jetifier": "^1.6.4",
"metro-react-native-babel-preset": "^0.56.0",
"metro-react-native-babel-preset": "^0.59.0",
"prettier": "2.0.2",
"react-native-safe-area-context": "^0.6.4",
"react-native-typescript-transformer": "^1.2.13",
......@@ -115,7 +115,6 @@
"typescript": "3.8.3"
},
"resolutions": {
"@react-native-community/eslint-config/babel-eslint": "10.0.3",
"kind-of": ">=6.0.3"
},
"detox": {
......
......@@ -31,7 +31,7 @@ import fontStyles from 'styles/fontStyles';
import { withAccountStore } from 'utils/HOC';
import { getIdentityName } from 'utils/identitiesUtils';
import {
getSeedPhrase,
unlockAndReturnSeed,
navigateToLegacyAccountList,
resetNavigationTo,
resetNavigationWithNetworkChooser
......@@ -74,7 +74,7 @@ function IdentitiesSwitch({
if (screenName === 'Main') {
resetNavigationTo(navigation, screenName, params);
} else if (screenName === 'IdentityBackup') {
const seedPhrase = await getSeedPhrase(navigation);
const seedPhrase = await unlockAndReturnSeed(navigation);
resetNavigationWithNetworkChooser(navigation, screenName, {
isNew: false,
seedPhrase
......
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -34,7 +34,7 @@ export default function MessageDetailsCard({
}): React.ReactElement {
return (
<View style={[styles.messageContainer, style]}>
<Text style={fontStyles.t_label}>{isHash ? 'Hash' : 'Message'}</Text>
<Text style={styles.titleText}>{isHash ? 'Hash' : 'Message'}</Text>
{isHash ? (
<Text style={styles.hashText}>{message}</Text>
) : (
......@@ -49,8 +49,7 @@ export default function MessageDetailsCard({
const styles = StyleSheet.create({
hashText: {
...fontStyles.t_codeS,
backgroundColor: colors.label_text,
color: colors.bg,
color: colors.label_text,
marginBottom: 20,
paddingHorizontal: 8
},
......@@ -64,5 +63,9 @@ const styles = StyleSheet.create({
marginBottom: 20,
minHeight: 120,
padding: 10
},
titleText: {
...fontStyles.t_label,
paddingHorizontal: 8
}
});
......@@ -134,8 +134,10 @@ const ExtrinsicPart = withRegistriesStore<ExtrinsicPartProps>(
if (period && phase) {
return (
<View style={styles.era}>
<Text style={{ ...styles.subLabel, flex: 1 }}>phase: {phase} </Text>
<Text style={{ ...styles.subLabel, flex: 1 }}>
<Text style={{ ...styles.secondaryText, flex: 1 }}>
phase: {phase}{' '}
</Text>
<Text style={{ ...styles.secondaryText, flex: 1 }}>
period: {period}
</Text>
</View>
......@@ -149,7 +151,9 @@ const ExtrinsicPart = withRegistriesStore<ExtrinsicPartProps>(
flexWrap: 'wrap'
}}
>
<Text style={{ ...styles.subLabel, flex: 1 }}>Immortal Era</Text>
<Text style={{ ...styles.secondaryText, flex: 1 }}>
Immortal Era
</Text>
<Text style={{ ...styles.secondaryText, flex: 3 }}>
{value.toString()}
</Text>
......@@ -182,7 +186,7 @@ const ExtrinsicPart = withRegistriesStore<ExtrinsicPartProps>(
return (
<View key={index} style={styles.callDetails}>
<Text style={styles.subLabel}>
<Text style={styles.secondaryText}>
Call <Text style={styles.titleText}>{sectionMethod}</Text> with
the following arguments:
</Text>
......@@ -347,12 +351,6 @@ const styles = StyleSheet.create({
paddingHorizontal: 8,
textAlign: 'left'
},
subLabel: {
...fontStyles.t_codeS,
color: colors.label_text,
paddingLeft: 8,
textAlign: 'left'
},
titleText: {
...fontStyles.t_codeS,
color: colors.label_text_sec
......
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
......@@ -14,145 +14,43 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { useEffect } from 'react';
import React, { useContext, useEffect, useState } from 'react';
import { Alert, Button, StyleSheet, Text, View } from 'react-native';
import { RNCamera } from 'react-native-camera';
import { Subscribe } from 'unstated';
import { processBarCode } from 'modules/sign/utils';
import { onMockBarCodeRead } from 'e2e/injections';
import { NavigationProps, NavigationScannerProps } from 'types/props';
import { SeedRefsContext } from 'stores/SeedRefStore';
import { NavigationAccountScannerProps } from 'types/props';
import colors from 'styles/colors';
import fonts from 'styles/fonts';
import AccountsStore from 'stores/AccountsStore';
import ScannerStore from 'stores/ScannerStore';
import { isAddressString, isJsonString, rawDataToU8A } from 'utils/decoders';
import ScreenHeading from 'components/ScreenHeading';
import { TxRequestData } from 'types/scannerTypes';
import { withAccountAndScannerStore } from 'utils/HOC';
interface State {
enableScan: boolean;
}
export default class Scanner extends React.PureComponent<
NavigationProps<'QrScanner'>,
State
> {
constructor(props: NavigationProps<'QrScanner'>) {
super(props);
this.state = { enableScan: true };
}
showErrorMessage(
scannerStore: ScannerStore,
title: string,
message: string
): void {
this.setState({ enableScan: false });
Alert.alert(title, message, [
{
onPress: async (): Promise<void> => {
await scannerStore.cleanup();
this.setState({ enableScan: true });
},
text: 'Try again'
}
]);
}
render(): React.ReactElement {
return (
<Subscribe to={[ScannerStore, AccountsStore]}>
{(
scannerStore: ScannerStore,
accountsStore: AccountsStore
): React.ReactElement => {
return (
<QrScannerView
completedFramesCount={scannerStore.getCompletedFramesCount()}
isMultipart={scannerStore.getTotalFramesCount() > 1}
missedFrames={scannerStore.getMissedFrames()}
navigation={this.props.navigation}
route={this.props.route}
scannerStore={scannerStore}
totalFramesCount={scannerStore.getTotalFramesCount()}
onBarCodeRead={async (
txRequestData: TxRequestData
): Promise<void> => {
if (scannerStore.isBusy() || !this.state.enableScan) {
return;
}
try {
if (isAddressString(txRequestData.data)) {
return this.showErrorMessage(
scannerStore,
text.ADDRESS_ERROR_TITLE,
text.ADDRESS_ERROR_MESSAGE
);
} else if (isJsonString(txRequestData.data)) {
// Ethereum Legacy
await scannerStore.setUnsigned(txRequestData.data);
} else if (!scannerStore.isMultipartComplete()) {
const strippedData = rawDataToU8A(txRequestData.rawData);
if (strippedData === null)
return this.showErrorMessage(
scannerStore,
text.PARSE_ERROR_TITLE,
'There is no raw Data from the request'
);
await scannerStore.setParsedData(
strippedData,
accountsStore,
false
);
}
if (scannerStore.getErrorMsg()) {
throw new Error(scannerStore.getErrorMsg());
}
if (scannerStore.getUnsigned()) {
await scannerStore.setData(accountsStore);
if (scannerStore.getType() === 'transaction') {
scannerStore.clearMultipartProgress();
this.props.navigation.navigate('TxDetails');
} else {
scannerStore.clearMultipartProgress();
this.props.navigation.navigate('MessageDetails');
}
}
} catch (e) {
return this.showErrorMessage(
scannerStore,
text.PARSE_ERROR_TITLE,
e.message
);
}
}}
/>
);
}}
</Subscribe>
);
}
}
interface ViewProps extends NavigationScannerProps<'QrScanner'> {
onBarCodeRead: (listener: TxRequestData) => void;
type Frames = {
completedFramesCount: number;
isMultipart: boolean;
missedFrames: number[];
missingFramesMessage: string;
totalFramesCount: number;
}
};
function QrScannerView({
export function Scanner({
navigation,
scannerStore,
...props
}: ViewProps): React.ReactElement {
if (global.inTest && global.scanRequest !== undefined) {
onMockBarCodeRead(global.scanRequest, props.onBarCodeRead);
}
accounts,
scannerStore
}: NavigationAccountScannerProps<'QrScanner'>): React.ReactElement {
const [seedRefs] = useContext<SeedRefsContext>(SeedRefsContext);
const [enableScan, setEnableScan] = useState<boolean>(true);
const [lastFrame, setLastFrame] = useState<null | string>(null);
const [multiFrames, setMultiFrames] = useState<Frames>({
completedFramesCount: 0,
isMultipart: false,
missedFrames: [],
missingFramesMessage: '',
totalFramesCount: 0
});
useEffect((): (() => void) => {
const unsubscribeFocus = navigation.addListener(
'focus',
......@@ -169,18 +67,71 @@ function QrScannerView({
};
}, [navigation, scannerStore]);
const missedFrames = scannerStore.getMissedFrames();
const missedFramesMessage = missedFrames && missedFrames.join(', ');
useEffect(() => {
const missedFrames = scannerStore.getMissedFrames();
setMultiFrames({
completedFramesCount: scannerStore.getCompletedFramesCount(),
isMultipart: scannerStore.getTotalFramesCount() > 1,
missedFrames,
missingFramesMessage: missedFrames && missedFrames.join(', '),
totalFramesCount: scannerStore.getTotalFramesCount()
});
}, [lastFrame, scannerStore.state.completedFramesCount, scannerStore]);
function showErrorMessage(title: string, message: string): void {
setEnableScan(false);
scannerStore.setBusy();
Alert.alert(title, message, [
{
onPress: async (): Promise<void> => {
await scannerStore.cleanup();
scannerStore.setReady();
setLastFrame(null);
setEnableScan(true);
},
text: 'Try again'
}
]);
}
async function onBarCodeRead(event: any): Promise<void> {
if (event.type !== RNCamera.Constants.BarCodeType.qr) return;
if (scannerStore.isBusy() || !enableScan) {
return;
}
if (event.rawData === lastFrame) {
return;
}
setLastFrame(event.rawData);
await processBarCode(
showErrorMessage,
event as TxRequestData,
navigation,
accounts,
scannerStore,
seedRefs
);
}
if (scannerStore.isBusy()) {
return <View style={styles.inactive} />;
if (global.inTest && global.scanRequest !== undefined) {
onMockBarCodeRead(
global.scanRequest,
async (tx: TxRequestData): Promise<void> => {
await onBarCodeRead(tx);
}
);
}
const {
completedFramesCount,
isMultipart,
missedFrames,
totalFramesCount,
missingFramesMessage
} = multiFrames;
return (
<RNCamera
captureAudio={false}
onBarCodeRead={(event: any): void =>
props.onBarCodeRead(event as TxRequestData)
}
onBarCodeRead={onBarCodeRead}
style={styles.view}
>
<View style={styles.body}>
......@@ -192,13 +143,13 @@ function QrScannerView({
<View style={styles.middleCenter} />
<View style={styles.middleRight} />
</View>
{props.isMultipart ? (
{isMultipart ? (
<View style={styles.bottom}>
<Text style={styles.descTitle}>
Scanning Multipart Data, Please Hold Still...
</Text>
<Text style={styles.descSecondary}>
{props.completedFramesCount} / {props.totalFramesCount} Completed.
{completedFramesCount} / {totalFramesCount} Completed.
</Text>
<Button
onPress={(): void => scannerStore.clearMultipartProgress()}
......@@ -214,7 +165,7 @@ function QrScannerView({
{missedFrames && missedFrames.length >= 1 && (
<View style={styles.bottom}>
<Text style={styles.descTitle}>
You missed the following frames: {missedFramesMessage}
Missing following frame(s): {missingFramesMessage}
</Text>
</View>
)}
......@@ -223,12 +174,7 @@ function QrScannerView({
);
}
const text = {
ADDRESS_ERROR_MESSAGE:
'Please create a transaction using a software such as MyCrypto or Fether so that Parity Signer can sign it.',
ADDRESS_ERROR_TITLE: 'Address detected',
PARSE_ERROR_TITLE: 'Unable to parse transaction'
};
export default withAccountAndScannerStore(Scanner);
const styles = StyleSheet.create({
body: {
......
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
......@@ -14,23 +14,36 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { isU8a, u8aToHex } from '@polkadot/util';
import React, { useEffect } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { Text, View } from 'react-native';
import strings from 'modules/sign/strings';
import CompatibleCard from 'components/CompatibleCard';
import PayloadDetailsCard from 'modules/sign/components/PayloadDetailsCard';
import { NETWORK_LIST } from 'constants/networkSpecs';
import { SafeAreaScrollViewContainer } from 'components/SafeAreaContainer';
import testIDs from 'e2e/testIDs';
import { NavigationScannerProps } from 'types/props';
import { isEthereumNetworkParams } from 'types/networkSpecsTypes';
import { NavigationAccountScannerProps } from 'types/props';
import QrView from 'components/QrView';
import { withScannerStore } from 'utils/HOC';
import fontStyles from 'styles/fontStyles';
import MessageDetailsCard from 'components/MessageDetailsCard';
import { withAccountAndScannerStore } from 'utils/HOC';
import styles from 'modules/sign/styles';
import MessageDetailsCard from 'modules/sign/components/MessageDetailsCard';
function SignedMessage({
accounts,
scannerStore
}: NavigationScannerProps<'SignedMessage'>): React.ReactElement {
}: NavigationAccountScannerProps<'SignedMessage'>): React.ReactElement {
const data = scannerStore.getSignedTxData();
const isHash = scannerStore.getIsHash();
const message = scannerStore.getMessage();
const message = scannerStore.getMessage()!;
const prehash = scannerStore.getPrehashPayload();
const dataToSign = scannerStore.getDataToSign()!;
const sender = scannerStore.getSender()!;
const senderNetworkParams = NETWORK_LIST[sender.networkKey];
const isEthereum = isEthereumNetworkParams(senderNetworkParams);
useEffect(
(): (() => void) =>
......@@ -46,24 +59,27 @@ function SignedMessage({
<View testID={testIDs.SignedMessage.qrView}>
<QrView data={data} />
</View>
<MessageDetailsCard
isHash={isHash}
message={message ?? ''}
data={data}
style={styles.messageDetail}
/>
<View style={styles.bodyContent}>
<Text style={styles.title}>From Account</Text>
<CompatibleCard account={sender} accountsStore={accounts} />
{!isEthereum && prehash ? (
<PayloadDetailsCard
description={strings.INFO_MULTI_PART}
payload={prehash}
signature={data}
networkKey={sender.networkKey}
/>
) : null}
<MessageDetailsCard
isHash={isHash ?? false}
message={message}
data={
isU8a(dataToSign) ? u8aToHex(dataToSign) : dataToSign.toString()
}
/>
</View>
</SafeAreaScrollViewContainer>
);
}
export default withScannerStore(SignedMessage);
const styles = StyleSheet.create({
messageDetail: {
paddingHorizontal: 20
},
topTitle: {
...fontStyles.h1,
textAlign: 'center'
}
});
export default withAccountAndScannerStore(SignedMessage);
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
......@@ -15,20 +15,22 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { useEffect } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { Text, View } from 'react-native';
import strings from 'modules/sign/strings';
import { SafeAreaScrollViewContainer } from 'components/SafeAreaContainer';
import { NETWORK_LIST } from 'constants/networkSpecs';
import testIDs from 'e2e/testIDs';
import { isEthereumNetworkParams } from 'types/networkSpecsTypes';
import { NavigationAccountScannerProps } from 'types/props';
import PayloadDetailsCard from 'components/PayloadDetailsCard';
import TxDetailsCard from 'components/TxDetailsCard';
import PayloadDetailsCard from 'modules/sign/components/PayloadDetailsCard';
import TxDetailsCard from 'modules/sign/components/TxDetailsCard';
import QrView from 'components/QrView';
import { withAccountAndScannerStore } from 'utils/HOC';
import fontStyles from 'styles/fontStyles';
import CompatibleCard from 'components/CompatibleCard';
import { Transaction } from 'utils/transaction';
import styles from 'modules/sign/styles';
function SignedTx({
scannerStore,
......@@ -36,7 +38,13 @@ function SignedTx({