Import.tsx 5.1 KB

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