@@ -14,71 +14,158 @@ See the License for the specific language governing permissions and
14
14
limitations under the License.
15
15
*/
16
16
17
+ import FileSaver from 'file-saver' ;
17
18
import React from 'react' ;
18
19
20
+ import * as Matrix from 'matrix-js-sdk' ;
21
+ import * as MegolmExportEncryption from '../../../utils/MegolmExportEncryption' ;
19
22
import sdk from '../../../index' ;
20
23
21
- import * as MegolmExportEncryption from '../../../utils/MegolmExportEncryption' ;
24
+ const PHASE_EDIT = 1 ;
25
+ const PHASE_EXPORTING = 2 ;
22
26
23
27
export default React . createClass ( {
24
28
displayName : 'ExportE2eKeysDialog' ,
25
29
30
+ propTypes : {
31
+ matrixClient : React . PropTypes . instanceOf ( Matrix . MatrixClient ) . isRequired ,
32
+ onFinished : React . PropTypes . func . isRequired ,
33
+ } ,
34
+
26
35
getInitialState : function ( ) {
27
36
return {
28
- collectedPassword : false ,
37
+ phase : PHASE_EDIT ,
38
+ errStr : null ,
29
39
} ;
30
40
} ,
31
41
42
+ componentWillMount : function ( ) {
43
+ this . _unmounted = false ;
44
+ } ,
45
+
46
+ componentWillUnmount : function ( ) {
47
+ this . _unmounted = true ;
48
+ } ,
49
+
32
50
_onPassphraseFormSubmit : function ( ev ) {
33
51
ev . preventDefault ( ) ;
34
- console . log ( this . refs . passphrase1 . value ) ;
52
+
53
+ const passphrase = this . refs . passphrase1 . value ;
54
+ if ( passphrase !== this . refs . passphrase2 . value ) {
55
+ this . setState ( { errStr : 'Passphrases must match' } ) ;
56
+ return false ;
57
+ }
58
+ if ( ! passphrase ) {
59
+ this . setState ( { errStr : 'Passphrase must not be empty' } ) ;
60
+ return false ;
61
+ }
62
+
63
+ this . _startExport ( passphrase ) ;
35
64
return false ;
36
65
} ,
37
66
38
- render : function ( ) {
39
- let content ;
40
- if ( ! this . state . collectedPassword ) {
41
- content = (
42
- < div className = "mx_Dialog_content" >
43
- < p >
44
- This process will allow you to export the keys for messages
45
- you have received in encrypted rooms to a local file. You
46
- will then be able to import the file into another Matrix
47
- client in the future, so that client will also be able to
48
- decrypt these messages.
49
- </ p >
50
- < p >
51
- The exported file will allow anyone who can read it to decrypt
52
- any encrypted messages that you can see, so you should be
53
- careful to keep it secure. To help with this, you should enter
54
- a passphrase below, which will be used to encrypt the exported
55
- data. It will only be possible to import the data by using the
56
- same passphrase.
57
- </ p >
58
- < form onSubmit = { this . _onPassphraseFormSubmit } >
59
- < div className = "mx_TextInputDialog_label" >
60
- < label htmlFor = "passphrase1" > Enter passphrase</ label >
61
- </ div >
62
- < div >
63
- < input ref = "passphrase1" id = "passphrase1"
64
- className = "mx_TextInputDialog_input"
65
- autoFocus = { true } size = "64" type = "password" />
66
- </ div >
67
- < div className = "mx_Dialog_buttons" >
68
- < input className = "mx_Dialog_primary" type = "submit" value = "Export" />
69
- </ div >
70
- </ form >
71
- </ div >
67
+ _startExport : function ( passphrase ) {
68
+ // extra Promise.resolve() to turn synchronous exceptions into
69
+ // asynchronous ones.
70
+ Promise . resolve ( ) . then ( ( ) => {
71
+ return this . props . matrixClient . exportRoomKeys ( ) ;
72
+ } ) . then ( ( k ) => {
73
+ return MegolmExportEncryption . encryptMegolmKeyFile (
74
+ JSON . stringify ( k ) , passphrase
72
75
) ;
73
- }
76
+ } ) . then ( ( f ) => {
77
+ const blob = new Blob ( [ f ] , {
78
+ type : 'text/plain;charset=us-ascii' ,
79
+ } ) ;
80
+ FileSaver . saveAs ( blob , 'riot-keys.txt' ) ;
81
+ this . props . onFinished ( true ) ;
82
+ } ) . catch ( ( e ) => {
83
+ if ( this . _unmounted ) {
84
+ return ;
85
+ }
86
+ this . setState ( {
87
+ errStr : e . message ,
88
+ phase : PHASE_EDIT ,
89
+ } ) ;
90
+ } ) ;
91
+
92
+ this . setState ( {
93
+ errStr : null ,
94
+ phase : PHASE_EXPORTING ,
95
+ } ) ;
96
+ } ,
97
+
98
+ render : function ( ) {
99
+ const BaseDialog = sdk . getComponent ( 'views.dialogs.BaseDialog' ) ;
100
+ const AccessibleButton = sdk . getComponent ( 'views.elements.AccessibleButton' ) ;
101
+
102
+ const disableForm = ( this . state . phase === PHASE_EXPORTING ) ;
74
103
75
104
return (
76
- < div className = "mx_exportE2eKeysDialog" >
77
- < div className = "mx_Dialog_title" >
78
- Export room keys
79
- </ div >
80
- { content }
81
- </ div >
105
+ < BaseDialog className = 'mx_exportE2eKeysDialog'
106
+ onFinished = { this . props . onFinished }
107
+ title = "Export room keys"
108
+ >
109
+ < form onSubmit = { this . _onPassphraseFormSubmit } >
110
+ < div className = "mx_Dialog_content" >
111
+ < p >
112
+ This process allows you to export the keys for messages
113
+ you have received in encrypted rooms to a local file. You
114
+ will then be able to import the file into another Matrix
115
+ client in the future, so that client will also be able to
116
+ decrypt these messages.
117
+ </ p >
118
+ < p >
119
+ The exported file will allow anyone who can read it to decrypt
120
+ any encrypted messages that you can see, so you should be
121
+ careful to keep it secure. To help with this, you should enter
122
+ a passphrase below, which will be used to encrypt the exported
123
+ data. It will only be possible to import the data by using the
124
+ same passphrase.
125
+ </ p >
126
+ < div className = 'error' >
127
+ { this . state . errStr }
128
+ </ div >
129
+ < div className = 'mx_E2eKeysDialog_inputTable' >
130
+ < div className = 'mx_E2eKeysDialog_inputRow' >
131
+ < div className = 'mx_E2eKeysDialog_inputLabel' >
132
+ < label htmlFor = 'passphrase1' >
133
+ Enter passphrase
134
+ </ label >
135
+ </ div >
136
+ < div className = 'mx_E2eKeysDialog_inputCell' >
137
+ < input ref = 'passphrase1' id = 'passphrase1'
138
+ autoFocus = { true } size = '64' type = 'password'
139
+ disabled = { disableForm }
140
+ />
141
+ </ div >
142
+ </ div >
143
+ < div className = 'mx_E2eKeysDialog_inputRow' >
144
+ < div className = 'mx_E2eKeysDialog_inputLabel' >
145
+ < label htmlFor = 'passphrase2' >
146
+ Confirm passphrase
147
+ </ label >
148
+ </ div >
149
+ < div className = 'mx_E2eKeysDialog_inputCell' >
150
+ < input ref = 'passphrase2' id = 'passphrase2'
151
+ size = '64' type = 'password'
152
+ disabled = { disableForm }
153
+ />
154
+ </ div >
155
+ </ div >
156
+ </ div >
157
+ </ div >
158
+ < div className = 'mx_Dialog_buttons' >
159
+ < input className = 'mx_Dialog_primary' type = 'submit' value = 'Export'
160
+ disabled = { disableForm }
161
+ />
162
+ < AccessibleButton element = 'button' onClick = { this . props . onFinished }
163
+ disabled = { disableForm } >
164
+ Cancel
165
+ </ AccessibleButton >
166
+ </ div >
167
+ </ form >
168
+ </ BaseDialog >
82
169
) ;
83
170
} ,
84
171
} ) ;
0 commit comments