Import.tsx 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. // Copyright 2017-2019 @polkadot/app-accounts authors & contributors
  2. // This software may be modified and distributed under the terms
  3. // of the Apache-2.0 license. See the LICENSE file for details.
  4. import { KeyringPair$Json } from '@polkadot/keyring/types';
  5. import { I18nProps } from '@polkadot/react-components/types';
  6. import { ActionStatus } from '@polkadot/react-components/Status/types';
  7. import { ModalProps } from '../types';
  8. import React from 'react';
  9. import { AddressRow, Button, InputAddress, InputFile, Modal, Password, TxComponent } from '@polkadot/react-components';
  10. import { isHex, isObject, u8aToString } from '@polkadot/util';
  11. import keyring from '@polkadot/ui-keyring';
  12. import translate from '../translate';
  13. interface Props extends ModalProps, I18nProps {}
  14. interface State {
  15. address: string | null;
  16. isFileValid: boolean;
  17. isPassValid: boolean;
  18. json: KeyringPair$Json | null;
  19. password: string;
  20. }
  21. class Import extends TxComponent<Props, State> {
  22. public state: State = {
  23. address: null,
  24. isFileValid: false,
  25. isPassValid: false,
  26. json: null,
  27. password: ''
  28. };
  29. public render (): React.ReactNode {
  30. const { onClose, t } = this.props;
  31. const { isFileValid, isPassValid } = this.state;
  32. return (
  33. <Modal
  34. dimmer='inverted'
  35. open
  36. >
  37. <Modal.Header>{t('Add via backup file')}</Modal.Header>
  38. {this.renderInput()}
  39. <Modal.Actions>
  40. <Button.Group>
  41. <Button
  42. icon='cancel'
  43. isNegative
  44. label={t('Cancel')}
  45. onClick={onClose}
  46. />
  47. <Button.Or />
  48. <Button
  49. icon='sync'
  50. isDisabled={!isFileValid || !isPassValid}
  51. isPrimary
  52. onClick={this.onSave}
  53. label={t('Restore')}
  54. ref={this.button}
  55. />
  56. </Button.Group>
  57. </Modal.Actions>
  58. </Modal>
  59. );
  60. }
  61. private renderInput (): React.ReactNode {
  62. const { t } = this.props;
  63. const { address, isFileValid, isPassValid, json, password } = this.state;
  64. const acceptedFormats = ['application/json', 'text/plain'].join(', ');
  65. return (
  66. <Modal.Content>
  67. <AddressRow
  68. defaultName={isFileValid && json ? json.meta.name : null}
  69. noDefaultNameOpacity
  70. value={isFileValid && address ? address : null}
  71. >
  72. <InputFile
  73. accept={acceptedFormats}
  74. className='full'
  75. help={t('Select the JSON key file that was downloaded when you created the account. This JSON file contains your private key encrypted with your password.')}
  76. isError={!isFileValid}
  77. label={t('backup file')}
  78. onChange={this.onChangeFile}
  79. withLabel
  80. />
  81. <Password
  82. autoFocus
  83. className='full'
  84. help={t('Type the password chosen at the account creation. It was used to encrypt your account\'s private key in the backup file.')}
  85. isError={!isPassValid}
  86. label={t('password')}
  87. onChange={this.onChangePass}
  88. onEnter={this.submit}
  89. value={password}
  90. />
  91. </AddressRow>
  92. </Modal.Content>
  93. );
  94. }
  95. private onChangeFile = (file: Uint8Array): void => {
  96. try {
  97. const json = JSON.parse(u8aToString(file));
  98. const publicKey = keyring.decodeAddress(json.address, true);
  99. const address = keyring.encodeAddress(publicKey);
  100. const isFileValid = publicKey.length === 32 && isHex(json.encoded) && isObject(json.meta) && (
  101. Array.isArray(json.encoding.content)
  102. ? json.encoding.content[0] === 'pkcs8'
  103. : json.encoding.content === 'pkcs8'
  104. );
  105. this.setState({
  106. address,
  107. isFileValid,
  108. json
  109. });
  110. } catch (error) {
  111. this.setState({
  112. address: null,
  113. isFileValid: false,
  114. json: null
  115. });
  116. console.error(error);
  117. }
  118. }
  119. private onChangePass = (password: string): void => {
  120. this.setState({
  121. isPassValid: keyring.isPassValid(password),
  122. password
  123. });
  124. }
  125. private onSave = (): void => {
  126. const { onClose, onStatusChange, t } = this.props;
  127. const { json, password } = this.state;
  128. if (!json) {
  129. return;
  130. }
  131. const status: Partial<ActionStatus> = { action: 'restore' };
  132. try {
  133. const pair = keyring.restoreAccount(json, password);
  134. const { address } = pair;
  135. status.status = pair ? 'success' : 'error';
  136. status.account = address;
  137. status.message = t('account restored');
  138. InputAddress.setLastValue('account', address);
  139. } catch (error) {
  140. this.setState({ isPassValid: false });
  141. status.status = 'error';
  142. status.message = error.message;
  143. console.error(error);
  144. }
  145. onStatusChange(status as ActionStatus);
  146. if (status.status !== 'error') {
  147. onClose();
  148. }
  149. }
  150. }
  151. export default translate(Import);