@@ -18,7 +18,10 @@ limitations under the License.
1818require ( "./index.scss" ) ;
1919
2020import * as qs from 'querystring' ;
21- import { Capability , WidgetApi } from "matrix-react-sdk/src/widgets/WidgetApi" ;
21+ import { Capability , WidgetApi } from 'matrix-react-sdk/src/widgets/WidgetApi' ;
22+ import { KJUR } from 'jsrsasign' ;
23+
24+ const JITSI_OPENIDTOKEN_JWT_AUTH = 'openidtoken-jwt' ;
2225
2326// Dev note: we use raw JS without many dependencies to reduce bundle size.
2427// We do not need all of React to render a Jitsi conference.
@@ -33,6 +36,8 @@ let conferenceId: string;
3336let displayName : string ;
3437let avatarUrl : string ;
3538let userId : string ;
39+ let jitsiAuth : string ;
40+ let roomId : string ;
3641
3742let widgetApi : WidgetApi ;
3843
@@ -69,40 +74,118 @@ let widgetApi: WidgetApi;
6974 displayName = qsParam ( 'displayName' , true ) ;
7075 avatarUrl = qsParam ( 'avatarUrl' , true ) ; // http not mxc
7176 userId = qsParam ( 'userId' ) ;
77+ jitsiAuth = qsParam ( 'auth' , true ) ;
78+ roomId = qsParam ( 'roomId' , true ) ;
7279
7380 if ( widgetApi ) {
7481 await widgetApi . waitReady ( ) ;
7582 await widgetApi . setAlwaysOnScreen ( false ) ; // start off as detachable from the screen
76- }
7783
78- // TODO: register widgetApi listeners for PTT controls (https://github.com/vector-im/riot-web/issues/12795)
79-
80- document . getElementById ( "joinButton" ) . onclick = ( ) => joinConference ( ) ;
84+ // See https://github.com/matrix-org/prosody-mod-auth-matrix-user-verification
85+ if ( jitsiAuth === JITSI_OPENIDTOKEN_JWT_AUTH ) {
86+ // Request credentials, give callback to continue when received
87+ widgetApi . requestOpenIDCredentials ( credentialsResponseCallback ) ;
88+ } else {
89+ enableJoinButton ( ) ;
90+ }
91+ // TODO: register widgetApi listeners for PTT controls (https://github.com/vector-im/riot-web/issues/12795)
92+ } else {
93+ enableJoinButton ( ) ;
94+ }
8195 } catch ( e ) {
8296 console . error ( "Error setting up Jitsi widget" , e ) ;
83- document . getElementById ( "jitsiContainer" ) . innerText = "Failed to load Jitsi widget" ;
84- switchVisibleContainers ( ) ;
97+ document . getElementById ( "widgetActionContainer" ) . innerText = "Failed to load Jitsi widget" ;
8598 }
8699} ) ( ) ;
87100
101+ /**
102+ * Enable or show error depending on what the credentials response is.
103+ */
104+ function credentialsResponseCallback ( ) {
105+ if ( widgetApi . openIDCredentials ) {
106+ console . info ( 'Successfully got OpenID credentials.' ) ;
107+ enableJoinButton ( ) ;
108+ } else {
109+ console . warn ( 'OpenID credentials request was blocked by user.' ) ;
110+ document . getElementById ( "widgetActionContainer" ) . innerText = "Failed to load Jitsi widget" ;
111+ }
112+ }
113+
114+ function enableJoinButton ( ) {
115+ document . getElementById ( "joinButton" ) . onclick = ( ) => joinConference ( ) ;
116+ }
117+
88118function switchVisibleContainers ( ) {
89119 inConference = ! inConference ;
90120 document . getElementById ( "jitsiContainer" ) . style . visibility = inConference ? 'unset' : 'hidden' ;
91121 document . getElementById ( "joinButtonContainer" ) . style . visibility = inConference ? 'hidden' : 'unset' ;
92122}
93123
124+ /**
125+ * Create a JWT token fot jitsi openidtoken-jwt auth
126+ *
127+ * See https://github.com/matrix-org/prosody-mod-auth-matrix-user-verification
128+ */
129+ function createJWTToken ( ) {
130+ // Header
131+ const header = { alg : 'HS256' , typ : 'JWT' } ;
132+ // Payload
133+ const payload = {
134+ // As per Jitsi token auth, `iss` needs to be set to something agreed between
135+ // JWT generating side and Prosody config. Since we have no configuration for
136+ // the widgets, we can't set one anywhere. Using the Jitsi domain here probably makes sense.
137+ iss : jitsiDomain ,
138+ sub : jitsiDomain ,
139+ aud : `https://${ jitsiDomain } ` ,
140+ room : "*" ,
141+ context : {
142+ matrix : {
143+ token : widgetApi . openIDCredentials . accessToken ,
144+ room_id : roomId ,
145+ } ,
146+ user : {
147+ avatar : avatarUrl ,
148+ name : displayName ,
149+ } ,
150+ } ,
151+ } ;
152+ // Sign JWT
153+ // The secret string here is irrelevant, we're only using the JWT
154+ // to transport data to Prosody in the Jitsi stack.
155+ return KJUR . jws . JWS . sign (
156+ 'HS256' ,
157+ JSON . stringify ( header ) ,
158+ JSON . stringify ( payload ) ,
159+ 'notused' ,
160+ ) ;
161+ }
162+
94163function joinConference ( ) { // event handler bound in HTML
164+ let jwt ;
165+ if ( jitsiAuth === JITSI_OPENIDTOKEN_JWT_AUTH ) {
166+ if ( ! widgetApi . openIDCredentials || ! widgetApi . openIDCredentials . accessToken ) {
167+ // We've failing to get a token, don't try to init conference
168+ console . warn ( 'Expected to have an OpenID credential, cannot initialize widget.' ) ;
169+ document . getElementById ( "widgetActionContainer" ) . innerText = "Failed to load Jitsi widget" ;
170+ return ;
171+ }
172+ jwt = createJWTToken ( ) ;
173+ }
174+
95175 switchVisibleContainers ( ) ;
96176
97- // noinspection JSIgnoredPromiseFromCall
98- if ( widgetApi ) widgetApi . setAlwaysOnScreen ( true ) ; // ignored promise because we don't care if it works
177+ if ( widgetApi ) {
178+ // ignored promise because we don't care if it works
179+ // noinspection JSIgnoredPromiseFromCall
180+ widgetApi . setAlwaysOnScreen ( true ) ;
181+ }
99182
100183 console . warn (
101184 "[Jitsi Widget] The next few errors about failing to parse URL parameters are fine if " +
102185 "they mention 'external_api' or 'jitsi' in the stack. They're just Jitsi Meet trying to parse " +
103186 "our fragment values and not recognizing the options." ,
104187 ) ;
105- const meetApi = new JitsiMeetExternalAPI ( jitsiDomain , {
188+ const options = {
106189 width : "100%" ,
107190 height : "100%" ,
108191 parentNode : document . querySelector ( "#jitsiContainer" ) ,
@@ -113,16 +196,22 @@ function joinConference() { // event handler bound in HTML
113196 MAIN_TOOLBAR_BUTTONS : [ ] ,
114197 VIDEO_LAYOUT_FIT : "height" ,
115198 } ,
116- } ) ;
199+ jwt : jwt ,
200+ } ;
201+
202+ const meetApi = new JitsiMeetExternalAPI ( jitsiDomain , options ) ;
117203 if ( displayName ) meetApi . executeCommand ( "displayName" , displayName ) ;
118204 if ( avatarUrl ) meetApi . executeCommand ( "avatarUrl" , avatarUrl ) ;
119205 if ( userId ) meetApi . executeCommand ( "email" , userId ) ;
120206
121207 meetApi . on ( "readyToClose" , ( ) => {
122208 switchVisibleContainers ( ) ;
123209
124- // noinspection JSIgnoredPromiseFromCall
125- if ( widgetApi ) widgetApi . setAlwaysOnScreen ( false ) ; // ignored promise because we don't care if it works
210+ if ( widgetApi ) {
211+ // ignored promise because we don't care if it works
212+ // noinspection JSIgnoredPromiseFromCall
213+ widgetApi . setAlwaysOnScreen ( false ) ;
214+ }
126215
127216 document . getElementById ( "jitsiContainer" ) . innerHTML = "" ;
128217 } ) ;
0 commit comments