From 64e43e4331e662eb88df9b815c79ec5692b611ae Mon Sep 17 00:00:00 2001 From: schrd Date: Mon, 17 Jun 2024 20:26:13 +0200 Subject: [PATCH] fix(core): 3.0 cluster setup (#20439) * Add graphql API endpoint to BBB API This parameter will be required for the HTML5 client to discover the API endpoint of the GraphQL API so it can fetch its settings. * Fix: ensure API calls are headed towards the BBB Server In a cluster setup the URL that is visible in the browser is different from the URL of the BBB Server. The clients needs to contact the BBB server to retrieve information about the graphQL websocket and the graphql API. This patch adds the GraphQL API endpoint to the BBB API and changes the Client to use it. * Make GraphQL API endpoint configurable The default value should work for single node and cluster setups * Update docs to reflect changes required by cluster setup * fix eslint and typescript errors * Fix cluster setup docs - add missing parts for hasura - use consistent domain example.com * rename variable Co-authored-by: Gustavo Trott * rename variable Co-authored-by: Gustavo Trott * rename variable Co-authored-by: Gustavo Trott * Update bigbluebutton-web/grails-app/controllers/org/bigbluebutton/web/controllers/ApiController.groovy Co-authored-by: Gustavo Trott * Update bigbluebutton-web/grails-app/conf/bigbluebutton.properties Co-authored-by: Gustavo Trott --------- Co-authored-by: Daniel Schreiber Co-authored-by: Gustavo Trott --- .../api/ParamsProcessorUtil.java | 9 ++++ .../api/util/ResponseBuilder.java | 3 +- .../connection-manager/component.tsx | 7 ++- .../startup-data-fetch/component.tsx | 42 +++++++++++------ .../components/settings-loader/component.tsx | 46 ++++++++++++------- .../grails-app/conf/bigbluebutton.properties | 3 ++ .../grails-app/conf/spring/resources.xml | 1 + .../web/controllers/ApiController.groovy | 2 + .../WEB-INF/freemarker/api-version.ftlx | 1 + docs/docs/administration/cluster-proxy.md | 30 ++++++++---- 10 files changed, 104 insertions(+), 40 deletions(-) diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/api/ParamsProcessorUtil.java b/bbb-common-web/src/main/java/org/bigbluebutton/api/ParamsProcessorUtil.java index ecf7580c85..15d8cf1779 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/api/ParamsProcessorUtil.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/api/ParamsProcessorUtil.java @@ -77,6 +77,7 @@ public class ParamsProcessorUtil { private String defaultHTML5ClientUrl; private String graphqlWebsocketUrl; + private String graphqlApiUrl; private Boolean allowRequestsWithoutSession = false; private Integer defaultHttpSessionTimeout = 14400; private Boolean useDefaultAvatar = false; @@ -886,6 +887,10 @@ public class ParamsProcessorUtil { return graphqlWebsocketUrl; } + public String getGraphqlApiUrl() { + return graphqlApiUrl; + } + public Boolean getUseDefaultLogo() { return useDefaultLogo; } @@ -1249,6 +1254,10 @@ public class ParamsProcessorUtil { this.graphqlWebsocketUrl = graphqlWebsocketUrl.replace("https://","wss://"); } + public void setGraphqlApiUrl(String graphqlApiUrl) { + this.graphqlApiUrl = graphqlApiUrl; + } + public void setUseDefaultLogo(Boolean value) { this.useDefaultLogo = value; } diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/api/util/ResponseBuilder.java b/bbb-common-web/src/main/java/org/bigbluebutton/api/util/ResponseBuilder.java index a907738e4b..11e5904c1c 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/api/util/ResponseBuilder.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/api/util/ResponseBuilder.java @@ -52,7 +52,7 @@ public class ResponseBuilder { return new Date(timestamp).toString(); } - public String buildMeetingVersion(String apiVersion, String bbbVersion, String graphqlWebsocketUrl, String returnCode) { + public String buildMeetingVersion(String apiVersion, String bbbVersion, String graphqlWebsocketUrl, String graphqlApiUrl, String returnCode) { StringWriter xmlText = new StringWriter(); Map data = new HashMap(); @@ -61,6 +61,7 @@ public class ResponseBuilder { data.put("apiVersion", apiVersion); data.put("bbbVersion", bbbVersion); data.put("graphqlWebsocketUrl", graphqlWebsocketUrl); + data.put("graphqlApiUrl", graphqlApiUrl); processData(getTemplate("api-version.ftlx"), data, xmlText); diff --git a/bigbluebutton-html5/imports/ui/components/connection-manager/component.tsx b/bigbluebutton-html5/imports/ui/components/connection-manager/component.tsx index c31644be96..4d01c739fd 100644 --- a/bigbluebutton-html5/imports/ui/components/connection-manager/component.tsx +++ b/bigbluebutton-html5/imports/ui/components/connection-manager/component.tsx @@ -70,7 +70,12 @@ const ConnectionManager: React.FC = ({ children }): Reac const numberOfAttempts = useRef(20); const [errorCounts, setErrorCounts] = React.useState(0); useEffect(() => { - fetch(`https://${window.location.hostname}/bigbluebutton/api`, { + const pathMatch = window.location.pathname.match('^(.*)/html5client/join$'); + if (pathMatch == null) { + throw new Error('Failed to match BBB client URI'); + } + const serverPathPrefix = pathMatch[1]; + fetch(`https://${window.location.hostname}${serverPathPrefix}/bigbluebutton/api`, { headers: { 'Content-Type': 'application/json', }, diff --git a/bigbluebutton-html5/imports/ui/components/connection-manager/startup-data-fetch/component.tsx b/bigbluebutton-html5/imports/ui/components/connection-manager/startup-data-fetch/component.tsx index 83e75166f6..5790464e57 100644 --- a/bigbluebutton-html5/imports/ui/components/connection-manager/startup-data-fetch/component.tsx +++ b/bigbluebutton-html5/imports/ui/components/connection-manager/startup-data-fetch/component.tsx @@ -1,5 +1,5 @@ import React, { useEffect } from 'react'; -import Session from '/imports/ui/services/storage/in-memory'; +import { Session } from 'meteor/session'; import { v4 as uuid } from 'uuid'; import { ErrorScreen } from '../../error-screen/component'; import LoadingScreen from '../../common/loading-screen/component'; @@ -65,20 +65,34 @@ const StartupDataFetch: React.FC = ({ setLoading(false); return; } - const clientStartupSettings = `/api/rest/clientStartupSettings/?sessionToken=${sessionToken}`; - const url = new URL(`${window.location.origin}${clientStartupSettings}`); - fetch(url, { method: 'get' }) - .then((resp) => resp.json()) - .then((data: Response) => { - const settings = data.meeting_clientSettings[0]; - sessionStorage.setItem('clientStartupSettings', JSON.stringify(settings || {})); - setSettingsFetched(true); - clearTimeout(timeoutRef.current); - setLoading(false); - }).catch(() => { - Session.setItem('errorMessageDescription', 'meeting_ended'); - setError('Error fetching startup data'); + const pathMatch = window.location.pathname.match('^(.*)/html5client/join$'); + if (pathMatch == null) { + throw new Error('Failed to match BBB client URI'); + } + const serverPathPrefix = pathMatch[1]; + fetch(`https://${window.location.hostname}${serverPathPrefix}/bigbluebutton/api`, { + headers: { + 'Content-Type': 'application/json', + }, + }).then((resp) => resp.json()) + .then((data) => { + const url = `${data.response.graphqlApiUrl}/clientStartupSettings/?sessionToken=${sessionToken}`; + fetch(url, { method: 'get', credentials: 'include' }) + .then((resp) => resp.json()) + .then((data: Response) => { + const settings = data.meeting_clientSettings[0]; + sessionStorage.setItem('clientStartupSettings', JSON.stringify(settings || {})); + setSettingsFetched(true); + clearTimeout(timeoutRef.current); + setLoading(false); + }).catch(() => { + Session.set('errorMessageDescription', 'meeting_ended'); + setError('Error fetching startup data'); + setLoading(false); + }); + }).catch((error) => { setLoading(false); + throw new Error('Error fetching GraphQL URL: '.concat(error.message || '')); }); }, []); diff --git a/bigbluebutton-html5/imports/ui/components/settings-loader/component.tsx b/bigbluebutton-html5/imports/ui/components/settings-loader/component.tsx index b31f89ed33..e441ee8d77 100644 --- a/bigbluebutton-html5/imports/ui/components/settings-loader/component.tsx +++ b/bigbluebutton-html5/imports/ui/components/settings-loader/component.tsx @@ -27,25 +27,39 @@ const SettingsLoader: React.FC = () => { }, []); useEffect(() => { - const urlParams = new URLSearchParams(window.location.search); - const sessionToken = urlParams.get('sessionToken'); - const clientStartupSettings = `/api/rest/clientSettings/?sessionToken=${sessionToken}`; - const url = new URL(`${window.location.origin}${clientStartupSettings}`); - fetch(url, { method: 'get' }) - .then((resp) => resp.json()) - .then((data: Response) => { - const settings = data?.meeting_clientSettings[0].clientSettingsJson; + const pathMatch = window.location.pathname.match('^(.*)/html5client/join$'); + if (pathMatch == null) { + throw new Error('Failed to match BBB client URI'); + } + const serverPathPrefix = pathMatch[1]; + fetch(`https://${window.location.hostname}${serverPathPrefix}/bigbluebutton/api`, { + headers: { + 'Content-Type': 'application/json', + }, + }).then((resp) => resp.json()) + .then((data) => { + const urlParams = new URLSearchParams(window.location.search); + const sessionToken = urlParams.get('sessionToken'); + const clientStartupSettings = `/clientSettings/?sessionToken=${sessionToken}`; + const url = new URL(`${data.response.graphqlApiUrl}${clientStartupSettings}`); + fetch(url, { method: 'get', credentials: 'include' }) + .then((resp) => resp.json()) + .then((data: Response) => { + const settings = data?.meeting_clientSettings[0].clientSettingsJson; - window.meetingClientSettings = JSON.parse(JSON.stringify(settings as unknown as MeetingClientSettings)); - const Meteor = { settings: {} }; - Meteor.settings = window.meetingClientSettings; - setMeetingSettings(settings as unknown as MeetingClientSettings); - setAllowToRender(true); - }).catch(() => { + window.meetingClientSettings = JSON.parse(JSON.stringify(settings as unknown as MeetingClientSettings)); + const Meteor = { settings: {} }; + Meteor.settings = window.meetingClientSettings; + setMeetingSettings(settings as unknown as MeetingClientSettings); + setAllowToRender(true); + }).catch(() => { + loadingContextInfo.setLoading(false, ''); + throw new Error('Error on requesting client settings data.'); + }); + }).catch((error) => { loadingContextInfo.setLoading(false, ''); - throw new Error('Error on requesting client settings data.'); + throw new Error('Error fetching GraphQL API URL: '.concat(error.message || '')); }); - // } }, []); return ( (allowToRender) diff --git a/bigbluebutton-web/grails-app/conf/bigbluebutton.properties b/bigbluebutton-web/grails-app/conf/bigbluebutton.properties index 034dd18d9d..ed69a3a114 100644 --- a/bigbluebutton-web/grails-app/conf/bigbluebutton.properties +++ b/bigbluebutton-web/grails-app/conf/bigbluebutton.properties @@ -308,6 +308,9 @@ defaultHTML5ClientUrl=${bigbluebutton.web.serverURL}/html5client/join # Using `serverURL` as default, so `https` will be automatically replaced by `wss` graphqlWebsocketUrl=${bigbluebutton.web.serverURL}/graphql +# Graphql API url (it's necessary to change for cluster setup) +graphqlApiUrl=${bigbluebutton.web.serverURL}/api/rest + # This parameter defines the duration (in minutes) to wait before removing user sessions after a meeting has ended. # During this delay, users can still access information indicating that the "Meeting has ended". # Setting this value to 0 will result in the sessions being kept alive indefinitely (permanent availability). diff --git a/bigbluebutton-web/grails-app/conf/spring/resources.xml b/bigbluebutton-web/grails-app/conf/spring/resources.xml index b6e9ea4831..8625110e32 100755 --- a/bigbluebutton-web/grails-app/conf/spring/resources.xml +++ b/bigbluebutton-web/grails-app/conf/spring/resources.xml @@ -142,6 +142,7 @@ with BigBlueButton; if not, see . + diff --git a/bigbluebutton-web/grails-app/controllers/org/bigbluebutton/web/controllers/ApiController.groovy b/bigbluebutton-web/grails-app/controllers/org/bigbluebutton/web/controllers/ApiController.groovy index 20f1ea25f6..2f8d37c1a2 100755 --- a/bigbluebutton-web/grails-app/controllers/org/bigbluebutton/web/controllers/ApiController.groovy +++ b/bigbluebutton-web/grails-app/controllers/org/bigbluebutton/web/controllers/ApiController.groovy @@ -97,6 +97,7 @@ class ApiController { apiVersion paramsProcessorUtil.getApiVersion() bbbVersion paramsProcessorUtil.getBbbVersion() graphqlWebsocketUrl paramsProcessorUtil.getGraphqlWebsocketUrl() + graphqlApiUrl paramsProcessorUtil.getGraphqlApiUrl() } render(contentType: "application/json", text: builder.toPrettyString()) } @@ -108,6 +109,7 @@ class ApiController { paramsProcessorUtil.getApiVersion(), paramsProcessorUtil.getBbbVersion(), paramsProcessorUtil.getGraphqlWebsocketUrl(), + paramsProcessorUtil.getGraphqlApiUrl(), RESP_CODE_SUCCESS), contentType: "text/xml") } diff --git a/bigbluebutton-web/src/main/webapp/WEB-INF/freemarker/api-version.ftlx b/bigbluebutton-web/src/main/webapp/WEB-INF/freemarker/api-version.ftlx index 05c0233ce9..3a0311a3cc 100644 --- a/bigbluebutton-web/src/main/webapp/WEB-INF/freemarker/api-version.ftlx +++ b/bigbluebutton-web/src/main/webapp/WEB-INF/freemarker/api-version.ftlx @@ -7,5 +7,6 @@ ${apiVersion} ${bbbVersion} ${graphqlWebsocketUrl} + ${graphqlApiUrl} diff --git a/docs/docs/administration/cluster-proxy.md b/docs/docs/administration/cluster-proxy.md index 8c9b886d33..462ad706e5 100644 --- a/docs/docs/administration/cluster-proxy.md +++ b/docs/docs/administration/cluster-proxy.md @@ -77,6 +77,12 @@ location /bbb-01/html5client/ { proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "Upgrade"; } +location /bbb-01/bigbluebutton/api { + proxy_pass https://bbb-01.example.com/bigbluebutton/api; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; +} ``` Repeat this `location` directive for every BigBlueButton server. @@ -123,9 +129,9 @@ public: url: 'https://bbb-01.example.com/pad' ``` -Create (or edit if it already exists) these unit file overrides: +Create (or edit if it already exists) this unit override file: -* `/usr/lib/systemd/system/bbb-html5.service` +* `/etc/systemd/system/bbb-html5.service.d/cluster.conf` It should have the following content: @@ -165,7 +171,7 @@ Create the file `/etc/bigbluebutton/etherpad.json` with the following content: ```json { "cluster_proxies": [ - "https://bbb-proxy.example.org" + "https://bbb-proxy.example.com" ] } ``` @@ -173,7 +179,7 @@ Create the file `/etc/bigbluebutton/etherpad.json` with the following content: Adjust the CORS settings in `/etc/default/bbb-web`: ```shell -JDK_JAVA_OPTIONS="-Dgrails.cors.enabled=true -Dgrails.cors.allowCredentials=true -Dgrails.cors.allowedOrigins=https://bbb-proxy.example.org,https://https://bbb-01.example.com" +JDK_JAVA_OPTIONS="-Dgrails.cors.enabled=true -Dgrails.cors.allowCredentials=true -Dgrails.cors.allowedOrigins=https://bbb-proxy.example.com,https://https://bbb-01.example.com" ``` Adjust the CORS setting in `/etc/default/bbb-graphql-middleware`: @@ -182,16 +188,24 @@ Adjust the CORS setting in `/etc/default/bbb-graphql-middleware`: BBB_GRAPHQL_MIDDLEWARE_LISTEN_PORT=8378 # If you are running a cluster proxy setup, you need to configure the Origin of # the frontend. See https://docs.bigbluebutton.org/administration/cluster-proxy -BBB_GRAPHQL_MIDDLEWARE_ORIGIN=bbb-proxy.example.org +BBB_GRAPHQL_MIDDLEWARE_ORIGIN=bbb-proxy.example.com ``` Pay attention that this one is without protocol, just the hostname. - -Restart BigBlueButton: +Adjust the CORS setting in `/etc/default/bbb-graphql-server`: ```shell -$ bbb-conf --restart +HASURA_GRAPHQL_CORS_DOMAIN="https://bbb-proxy.example.com" +``` + +This one includes the protocol. + +Reload systemd and restart BigBlueButton: + +```shell +# systemctl daemon-reload +# bbb-conf --restart ``` Now, opening a new session should show