@@ -17,6 +17,7 @@ limitations under the License.
17
17
'use strict' ;
18
18
19
19
import url from 'url' ;
20
+ import qs from 'querystring' ;
20
21
import React from 'react' ;
21
22
import MatrixClientPeg from '../../../MatrixClientPeg' ;
22
23
import PlatformPeg from '../../../PlatformPeg' ;
@@ -51,42 +52,63 @@ export default React.createClass({
51
52
creatorUserId : React . PropTypes . string ,
52
53
} ,
53
54
54
- getDefaultProps : function ( ) {
55
+ getDefaultProps ( ) {
55
56
return {
56
57
url : "" ,
57
58
} ;
58
59
} ,
59
60
60
- getInitialState : function ( ) {
61
- const widgetPermissionId = [ this . props . room . roomId , encodeURIComponent ( this . props . url ) ] . join ( '_' ) ;
61
+ /**
62
+ * Set initial component state when the App wUrl (widget URL) is being updated.
63
+ * Component props *must* be passed (rather than relying on this.props).
64
+ * @param {Object } newProps The new properties of the component
65
+ * @return {Object } Updated component state to be set with setState
66
+ */
67
+ _getNewState ( newProps ) {
68
+ const widgetPermissionId = [ newProps . room . roomId , encodeURIComponent ( newProps . url ) ] . join ( '_' ) ;
62
69
const hasPermissionToLoad = localStorage . getItem ( widgetPermissionId ) ;
63
70
return {
64
- loading : false ,
65
- widgetUrl : this . props . url ,
71
+ initialising : true , // True while we are mangling the widget URL
72
+ loading : true , // True while the iframe content is loading
73
+ widgetUrl : newProps . url ,
66
74
widgetPermissionId : widgetPermissionId ,
67
- // Assume that widget has permission to load if we are the user who added it to the room, or if explicitly granted by the user
68
- hasPermissionToLoad : hasPermissionToLoad === 'true' || this . props . userId === this . props . creatorUserId ,
75
+ // Assume that widget has permission to load if we are the user who
76
+ // added it to the room, or if explicitly granted by the user
77
+ hasPermissionToLoad : hasPermissionToLoad === 'true' || newProps . userId === newProps . creatorUserId ,
69
78
error : null ,
70
79
deleting : false ,
71
80
} ;
72
81
} ,
73
82
74
- // Returns true if props.url is a scalar URL, typically https://scalar.vector.im/api
75
- isScalarUrl : function ( ) {
83
+ getInitialState ( ) {
84
+ return this . _getNewState ( this . props ) ;
85
+ } ,
86
+
87
+ /**
88
+ * Returns true if specified url is a scalar URL, typically https://scalar.vector.im/api
89
+ * @param {[type] } url URL to check
90
+ * @return {Boolean } True if specified URL is a scalar URL
91
+ */
92
+ isScalarUrl ( url ) {
93
+ if ( ! url ) {
94
+ console . error ( 'Scalar URL check failed. No URL specified' ) ;
95
+ return false ;
96
+ }
97
+
76
98
let scalarUrls = SdkConfig . get ( ) . integrations_widgets_urls ;
77
99
if ( ! scalarUrls || scalarUrls . length == 0 ) {
78
100
scalarUrls = [ SdkConfig . get ( ) . integrations_rest_url ] ;
79
101
}
80
102
81
103
for ( let i = 0 ; i < scalarUrls . length ; i ++ ) {
82
- if ( this . props . url . startsWith ( scalarUrls [ i ] ) ) {
104
+ if ( url . startsWith ( scalarUrls [ i ] ) ) {
83
105
return true ;
84
106
}
85
107
}
86
108
return false ;
87
109
} ,
88
110
89
- isMixedContent : function ( ) {
111
+ isMixedContent ( ) {
90
112
const parentContentProtocol = window . location . protocol ;
91
113
const u = url . parse ( this . props . url ) ;
92
114
const childContentProtocol = u . protocol ;
@@ -98,43 +120,73 @@ export default React.createClass({
98
120
return false ;
99
121
} ,
100
122
101
- componentWillMount : function ( ) {
102
- if ( ! this . isScalarUrl ( ) ) {
123
+ componentWillMount ( ) {
124
+ window . addEventListener ( 'message' , this . _onMessage , false ) ;
125
+ this . setScalarToken ( ) ;
126
+ } ,
127
+
128
+ /**
129
+ * Adds a scalar token to the widget URL, if required
130
+ * Component initialisation is only complete when this function has resolved
131
+ */
132
+ setScalarToken ( ) {
133
+ this . setState ( { initialising : true } ) ;
134
+
135
+ if ( ! this . isScalarUrl ( this . props . url ) ) {
136
+ console . warn ( 'Non-scalar widget, not setting scalar token!' , url ) ;
137
+ this . setState ( {
138
+ error : null ,
139
+ widgetUrl : this . props . url ,
140
+ initialising : false ,
141
+ } ) ;
103
142
return ;
104
143
}
105
- // Fetch the token before loading the iframe as we need to mangle the URL
106
- this . setState ( {
107
- loading : true ,
108
- } ) ;
109
- this . _scalarClient = new ScalarAuthClient ( ) ;
144
+
145
+ // Fetch the token before loading the iframe as we need it to mangle the URL
146
+ if ( ! this . _scalarClient ) {
147
+ this . _scalarClient = new ScalarAuthClient ( ) ;
148
+ }
110
149
this . _scalarClient . getScalarToken ( ) . done ( ( token ) => {
111
- // Append scalar_token as a query param
150
+ // Append scalar_token as a query param if not already present
112
151
this . _scalarClient . scalarToken = token ;
113
152
const u = url . parse ( this . props . url ) ;
114
- if ( ! u . search ) {
115
- u . search = "?scalar_token=" + encodeURIComponent ( token ) ;
116
- } else {
117
- u . search += "&scalar_token=" + encodeURIComponent ( token ) ;
153
+ const params = qs . parse ( u . query ) ;
154
+ if ( ! params . scalar_token ) {
155
+ params . scalar_token = encodeURIComponent ( token ) ;
156
+ // u.search must be set to undefined, so that u.format() uses query paramerters - https://nodejs.org/docs/latest/api/url.html#url_url_format_url_options
157
+ u . search = undefined ;
158
+ u . query = params ;
118
159
}
119
160
120
161
this . setState ( {
121
162
error : null ,
122
163
widgetUrl : u . format ( ) ,
123
- loading : false ,
164
+ initialising : false ,
124
165
} ) ;
125
166
} , ( err ) => {
167
+ console . error ( "Failed to get scalar_token" , err ) ;
126
168
this . setState ( {
127
169
error : err . message ,
128
- loading : false ,
170
+ initialising : false ,
129
171
} ) ;
130
172
} ) ;
131
- window . addEventListener ( 'message' , this . _onMessage , false ) ;
132
173
} ,
133
174
134
175
componentWillUnmount ( ) {
135
176
window . removeEventListener ( 'message' , this . _onMessage ) ;
136
177
} ,
137
178
179
+ componentWillReceiveProps ( nextProps ) {
180
+ if ( nextProps . url !== this . props . url ) {
181
+ this . _getNewState ( nextProps ) ;
182
+ this . setScalarToken ( ) ;
183
+ } else if ( nextProps . show && ! this . props . show ) {
184
+ this . setState ( {
185
+ loading : true ,
186
+ } ) ;
187
+ }
188
+ } ,
189
+
138
190
_onMessage ( event ) {
139
191
if ( this . props . type !== 'jitsi' ) {
140
192
return ;
@@ -154,11 +206,11 @@ export default React.createClass({
154
206
}
155
207
} ,
156
208
157
- _canUserModify : function ( ) {
209
+ _canUserModify ( ) {
158
210
return WidgetUtils . canUserModifyWidgets ( this . props . room . roomId ) ;
159
211
} ,
160
212
161
- _onEditClick : function ( e ) {
213
+ _onEditClick ( e ) {
162
214
console . log ( "Edit widget ID " , this . props . id ) ;
163
215
const IntegrationsManager = sdk . getComponent ( "views.settings.IntegrationsManager" ) ;
164
216
const src = this . _scalarClient . getScalarInterfaceUrlForRoom (
@@ -168,9 +220,10 @@ export default React.createClass({
168
220
} , "mx_IntegrationsManager" ) ;
169
221
} ,
170
222
171
- /* If user has permission to modify widgets, delete the widget, otherwise revoke access for the widget to load in the user's browser
223
+ /* If user has permission to modify widgets, delete the widget,
224
+ * otherwise revoke access for the widget to load in the user's browser
172
225
*/
173
- _onDeleteClick : function ( ) {
226
+ _onDeleteClick ( ) {
174
227
if ( this . _canUserModify ( ) ) {
175
228
// Show delete confirmation dialog
176
229
const QuestionDialog = sdk . getComponent ( "dialogs.QuestionDialog" ) ;
@@ -202,6 +255,10 @@ export default React.createClass({
202
255
}
203
256
} ,
204
257
258
+ _onLoaded ( ) {
259
+ this . setState ( { loading : false } ) ;
260
+ } ,
261
+
205
262
// Widget labels to render, depending upon user permissions
206
263
// These strings are translated at the point that they are inserted in to the DOM, in the render method
207
264
_deleteWidgetLabel ( ) {
@@ -224,15 +281,15 @@ export default React.createClass({
224
281
this . setState ( { hasPermissionToLoad : false } ) ;
225
282
} ,
226
283
227
- formatAppTileName : function ( ) {
284
+ formatAppTileName ( ) {
228
285
let appTileName = "No name" ;
229
286
if ( this . props . name && this . props . name . trim ( ) ) {
230
287
appTileName = this . props . name . trim ( ) ;
231
288
}
232
289
return appTileName ;
233
290
} ,
234
291
235
- onClickMenuBar : function ( ev ) {
292
+ onClickMenuBar ( ev ) {
236
293
ev . preventDefault ( ) ;
237
294
238
295
// Ignore clicks on menu bar children
@@ -247,7 +304,7 @@ export default React.createClass({
247
304
} ) ;
248
305
} ,
249
306
250
- render : function ( ) {
307
+ render ( ) {
251
308
let appTileBody ;
252
309
253
310
// Don't render widget if it is in the process of being deleted
@@ -269,29 +326,30 @@ export default React.createClass({
269
326
}
270
327
271
328
if ( this . props . show ) {
272
- if ( this . state . loading ) {
273
- appTileBody = (
274
- < div className = 'mx_AppTileBody mx_AppLoading' >
275
- < MessageSpinner msg = 'Loading...' />
276
- </ div >
277
- ) ;
329
+ const loadingElement = (
330
+ < div className = 'mx_AppTileBody mx_AppLoading' >
331
+ < MessageSpinner msg = 'Loading...' />
332
+ </ div >
333
+ ) ;
334
+ if ( this . state . initialising ) {
335
+ appTileBody = loadingElement ;
278
336
} else if ( this . state . hasPermissionToLoad == true ) {
279
337
if ( this . isMixedContent ( ) ) {
280
338
appTileBody = (
281
339
< div className = "mx_AppTileBody" >
282
- < AppWarning
283
- errorMsg = "Error - Mixed content"
284
- />
340
+ < AppWarning errorMsg = "Error - Mixed content" />
285
341
</ div >
286
342
) ;
287
343
} else {
288
344
appTileBody = (
289
- < div className = "mx_AppTileBody" >
345
+ < div className = { this . state . loading ? 'mx_AppTileBody mx_AppLoading' : 'mx_AppTileBody' } >
346
+ { this . state . loading && loadingElement }
290
347
< iframe
291
348
ref = "appFrame"
292
349
src = { safeWidgetUrl }
293
350
allowFullScreen = "true"
294
351
sandbox = { sandboxFlags }
352
+ onLoad = { this . _onLoaded }
295
353
> </ iframe >
296
354
</ div >
297
355
) ;
0 commit comments