Migrate auth and settings to graphQL (#19507)
This commit is contained in:
parent
5518dff623
commit
58a0efe708
@ -190,4 +190,4 @@ select_permissions:
|
||||
filter:
|
||||
meetingId:
|
||||
_eq: X-Hasura-MeetingId
|
||||
comment: ""
|
||||
comment: ""
|
@ -22,4 +22,4 @@ select_permissions:
|
||||
filter:
|
||||
meetingId:
|
||||
_eq: X-Hasura-MeetingId
|
||||
comment: ""
|
||||
comment: ""
|
@ -212,4 +212,4 @@ update_permissions:
|
||||
_eq: X-Hasura-MeetingId
|
||||
- userId:
|
||||
_eq: X-Hasura-UserId
|
||||
check: null
|
||||
check: null
|
41
bigbluebutton-html5/client/clientStartup.tsx
Normal file
41
bigbluebutton-html5/client/clientStartup.tsx
Normal file
@ -0,0 +1,41 @@
|
||||
import React, {
|
||||
Suspense,
|
||||
useContext,
|
||||
useEffect,
|
||||
} from 'react';
|
||||
import { LoadingContext } from '/imports/ui/components/common/loading-screen/loading-screen-HOC/component';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
const MeetingClientLazy = React.lazy(() => import('./meetingClient'));
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
loadingClientLabel: {
|
||||
id: 'app.meeting.loadingClient',
|
||||
description: 'loading client label',
|
||||
},
|
||||
});
|
||||
|
||||
const ClientStartup: React.FC = () => {
|
||||
const loadingContextInfo = useContext(LoadingContext);
|
||||
const intl = useIntl();
|
||||
useEffect(() => {
|
||||
loadingContextInfo.setLoading(true, intl.formatMessage(intlMessages.loadingClientLabel));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Suspense>
|
||||
{
|
||||
(() => {
|
||||
try {
|
||||
return <MeetingClientLazy />;
|
||||
} catch (error) {
|
||||
loadingContextInfo.setLoading(false, '');
|
||||
throw new Error('Error on rendering MeetingClientLazy: '.concat(JSON.stringify(error) || ''));
|
||||
}
|
||||
})()
|
||||
}
|
||||
</Suspense>
|
||||
);
|
||||
};
|
||||
|
||||
export default ClientStartup;
|
38
bigbluebutton-html5/client/main.tsx
Normal file
38
bigbluebutton-html5/client/main.tsx
Normal file
@ -0,0 +1,38 @@
|
||||
import React from 'react';
|
||||
import ConnectionManager from '/imports/ui/components/connection-manager/component';
|
||||
// eslint-disable-next-line react/no-deprecated
|
||||
import { render } from 'react-dom';
|
||||
import SettingsLoader from '/imports/ui/components/settings-loader/component';
|
||||
import ErrorBoundary from '/imports/ui/components/common/error-boundary/component';
|
||||
import { ErrorScreen } from '/imports/ui/components/error-screen/component';
|
||||
import PresenceManager from '/imports/ui/components/join-handler/presenceManager/component';
|
||||
import LoadingScreenHOC from '/imports/ui/components/common/loading-screen/loading-screen-HOC/component';
|
||||
import IntlLoaderContainer from '/imports/startup/client/intlLoader';
|
||||
import LocatedErrorBoundary from '/imports/ui/components/common/error-boundary/located-error-boundary/component';
|
||||
import StartupDataFetch from '/imports/ui/components/connection-manager/startup-data-fetch/component';
|
||||
|
||||
const Main: React.FC = () => {
|
||||
return (
|
||||
<StartupDataFetch>
|
||||
<ErrorBoundary Fallback={ErrorScreen}>
|
||||
<LoadingScreenHOC>
|
||||
<IntlLoaderContainer>
|
||||
{/* from there the error messages are located */}
|
||||
<LocatedErrorBoundary Fallback={ErrorScreen}>
|
||||
<ConnectionManager>
|
||||
<PresenceManager>
|
||||
<SettingsLoader />
|
||||
</PresenceManager>
|
||||
</ConnectionManager>
|
||||
</LocatedErrorBoundary>
|
||||
</IntlLoaderContainer>
|
||||
</LoadingScreenHOC>
|
||||
</ErrorBoundary>
|
||||
</StartupDataFetch>
|
||||
);
|
||||
};
|
||||
|
||||
render(
|
||||
<Main />,
|
||||
document.getElementById('app'),
|
||||
);
|
@ -17,43 +17,49 @@
|
||||
*/
|
||||
/* eslint no-unused-vars: 0 */
|
||||
|
||||
import React from 'react';
|
||||
import React, { useContext, useEffect } from 'react';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { render } from 'react-dom';
|
||||
import logger from '/imports/startup/client/logger';
|
||||
import '/imports/ui/services/mobile-app';
|
||||
import Base from '/imports/startup/client/base';
|
||||
import JoinHandler from '/imports/ui/components/join-handler/component';
|
||||
import JoinHandler from '../imports/ui/components/join-handler/component';
|
||||
import AuthenticatedHandler from '/imports/ui/components/authenticated-handler/component';
|
||||
import Subscriptions from '/imports/ui/components/subscriptions/component';
|
||||
import IntlStartup from '/imports/startup/client/intl';
|
||||
import ContextProviders from '/imports/ui/components/context-providers/component';
|
||||
import UsersAdapter from '/imports/ui/components/components-data/users-context/adapter';
|
||||
import GraphqlProvider from '/imports/ui/components/graphql-provider/component';
|
||||
import ConnectionManager from '/imports/ui/components/connection-manager/component';
|
||||
import { liveDataEventBrokerInitializer } from '/imports/ui/services/LiveDataEventBroker/LiveDataEventBroker';
|
||||
// The adapter import is "unused" as far as static code is concerned, but it
|
||||
// needs to here to override global prototypes. So: don't remove it - prlanzarin 25 Apr 2022
|
||||
import adapter from 'webrtc-adapter';
|
||||
|
||||
import collectionMirrorInitializer from './collection-mirror-initializer';
|
||||
import { LoadingContext } from '/imports/ui/components/common/loading-screen/loading-screen-HOC/component';
|
||||
import IntlAdapter from '/imports/startup/client/intlAdapter';
|
||||
import PresenceAdapter from '/imports/ui/components/authenticated-handler/presence-adapter/component';
|
||||
import CustomUsersSettings from '/imports/ui/components/join-handler/custom-users-settings/component';
|
||||
|
||||
import('/imports/api/audio/client/bridge/bridge-whitelist').catch(() => {
|
||||
// bridge loading
|
||||
});
|
||||
|
||||
const { disableWebsocketFallback } = Meteor.settings.public.app;
|
||||
|
||||
if (disableWebsocketFallback) {
|
||||
Meteor.connection._stream._sockjsProtocolsWhitelist = function () { return ['websocket']; };
|
||||
|
||||
Meteor.disconnect();
|
||||
Meteor.reconnect();
|
||||
}
|
||||
|
||||
collectionMirrorInitializer();
|
||||
liveDataEventBrokerInitializer();
|
||||
|
||||
Meteor.startup(() => {
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
const Startup = () => {
|
||||
const loadingContextInfo = useContext(LoadingContext);
|
||||
useEffect(() => {
|
||||
const { disableWebsocketFallback } = window.meetingClientSettings.public.app;
|
||||
loadingContextInfo.setLoading(false, '');
|
||||
if (disableWebsocketFallback) {
|
||||
Meteor.connection._stream._sockjsProtocolsWhitelist = function () { return ['websocket']; };
|
||||
|
||||
Meteor.disconnect();
|
||||
Meteor.reconnect();
|
||||
}
|
||||
}, []);
|
||||
// Logs all uncaught exceptions to the client logger
|
||||
window.addEventListener('error', (e) => {
|
||||
let message = e.message || e.error.toString();
|
||||
@ -76,24 +82,20 @@ Meteor.startup(() => {
|
||||
}, message);
|
||||
});
|
||||
|
||||
// TODO make this a Promise
|
||||
render(
|
||||
return (
|
||||
<ContextProviders>
|
||||
<>
|
||||
<JoinHandler>
|
||||
<AuthenticatedHandler>
|
||||
<GraphqlProvider>
|
||||
<Subscriptions>
|
||||
<IntlStartup>
|
||||
<Base />
|
||||
</IntlStartup>
|
||||
</Subscriptions>
|
||||
</GraphqlProvider>
|
||||
</AuthenticatedHandler>
|
||||
</JoinHandler>
|
||||
<PresenceAdapter>
|
||||
<Subscriptions>
|
||||
<IntlAdapter>
|
||||
<Base />
|
||||
</IntlAdapter>
|
||||
</Subscriptions>
|
||||
</PresenceAdapter>
|
||||
<UsersAdapter />
|
||||
</>
|
||||
</ContextProviders>,
|
||||
document.getElementById('app'),
|
||||
</ContextProviders>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
export default Startup;
|
@ -2,8 +2,10 @@ import { Meteor } from 'meteor/meteor';
|
||||
import validateAuthToken from './methods/validateAuthToken';
|
||||
import setUserEffectiveConnectionType from './methods/setUserEffectiveConnectionType';
|
||||
import userActivitySign from './methods/userActivitySign';
|
||||
import validateConnection from './methods/validateConnection';
|
||||
|
||||
Meteor.methods({
|
||||
validateConnection,
|
||||
validateAuthToken,
|
||||
setUserEffectiveConnectionType,
|
||||
userActivitySign,
|
||||
|
@ -0,0 +1,65 @@
|
||||
import { Client } from 'pg';
|
||||
import upsertValidationState from '/imports/api/auth-token-validation/server/modifiers/upsertValidationState';
|
||||
import { ValidationStates } from '/imports/api/auth-token-validation';
|
||||
import userJoin from '/imports/api/users/server/handlers/userJoin';
|
||||
import Users from '/imports/api/users';
|
||||
|
||||
import createDummyUser from '../modifiers/createDummyUser';
|
||||
import updateUserConnectionId from '../modifiers/updateUserConnectionId';
|
||||
|
||||
async function validateConnection(requesterToken, meetingId, userId) {
|
||||
try {
|
||||
const client = new Client({
|
||||
host: process.env.POSTGRES_HOST || Meteor.settings.private.postgresql.host,
|
||||
port: process.env.POSTGRES_PORT || Meteor.settings.private.postgresql.port,
|
||||
database: process.env.POSTGRES_HOST || Meteor.settings.private.postgresql.database,
|
||||
user: process.env.POSTGRES_USER || Meteor.settings.private.postgresql.user,
|
||||
password: process.env.POSTGRES_PASSWORD || Meteor.settings.private.postgresql.password,
|
||||
query_timeout: process.env.POSTGRES_TIMEOUT || Meteor.settings.private.postgresql.timeout,
|
||||
});
|
||||
|
||||
await client.connect();
|
||||
|
||||
const res = await client.query('select "meetingId", "userId" from v_user_connection_auth where "authToken" = $1', [requesterToken]);
|
||||
|
||||
if (res.rows.length === 0) {
|
||||
await upsertValidationState(
|
||||
meetingId,
|
||||
userId,
|
||||
ValidationStates.INVALID,
|
||||
this.connection.id,
|
||||
);
|
||||
} else {
|
||||
const sessionId = `${meetingId}--${userId}`;
|
||||
this.setUserId(sessionId);
|
||||
await upsertValidationState(
|
||||
meetingId,
|
||||
userId,
|
||||
ValidationStates.VALIDATED,
|
||||
this.connection.id,
|
||||
);
|
||||
|
||||
const User = await Users.findOneAsync({
|
||||
meetingId,
|
||||
userId,
|
||||
});
|
||||
|
||||
if (!User) {
|
||||
await createDummyUser(meetingId, userId, requesterToken);
|
||||
} else {
|
||||
await updateUserConnectionId(meetingId, userId, this.connection.id);
|
||||
}
|
||||
userJoin(meetingId, userId, requesterToken);
|
||||
}
|
||||
await client.end();
|
||||
} catch (e) {
|
||||
await upsertValidationState(
|
||||
meetingId,
|
||||
userId,
|
||||
ValidationStates.INVALID,
|
||||
this.connection.id,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default validateConnection;
|
@ -25,7 +25,7 @@ import { useMutation } from '@apollo/client';
|
||||
import { SET_EXIT_REASON } from '/imports/ui/core/graphql/mutations/userMutations';
|
||||
import useUserChangedLocalSettings from '/imports/ui/services/settings/hooks/useUserChangedLocalSettings';
|
||||
|
||||
const CHAT_CONFIG = Meteor.settings.public.chat;
|
||||
const CHAT_CONFIG = window.meetingClientSettings.public.chat;
|
||||
const PUBLIC_CHAT_ID = CHAT_CONFIG.public_group_id;
|
||||
const USER_WAS_EJECTED = 'userWasEjected';
|
||||
|
||||
@ -395,7 +395,7 @@ export default withTracker(() => {
|
||||
currentConnectionId: 1,
|
||||
connectionIdUpdateTime: 1,
|
||||
};
|
||||
const User = Users.findOne({ intId: credentials.requesterUserId }, { fields });
|
||||
const User = Users.findOne({ userId: credentials.requesterUserId }, { fields });
|
||||
const meeting = Meetings.findOne({ meetingId }, {
|
||||
fields: {
|
||||
meetingEnded: 1,
|
||||
@ -412,30 +412,21 @@ export default withTracker(() => {
|
||||
const ejected = User?.ejected;
|
||||
const ejectedReason = User?.ejectedReason;
|
||||
const meetingEndedReason = meeting?.meetingEndedReason;
|
||||
const currentConnectionId = User?.currentConnectionId;
|
||||
const { connectionID, connectionAuthTime } = Auth;
|
||||
const connectionIdUpdateTime = User?.connectionIdUpdateTime;
|
||||
|
||||
|
||||
if (ejected) {
|
||||
// use the connectionID to block users, so we can detect if the user was
|
||||
// blocked by the current connection. This is the case when a a user is
|
||||
// ejected from a meeting but not permanently ejected. Permanent ejects are
|
||||
// managed by the server, not by the client.
|
||||
BBBStorage.setItem(USER_WAS_EJECTED, connectionID);
|
||||
}
|
||||
|
||||
if (currentConnectionId && currentConnectionId !== connectionID && connectionIdUpdateTime > connectionAuthTime) {
|
||||
Session.set('codeError', '409');
|
||||
Session.set('errorMessageDescription', 'joined_another_window_reason')
|
||||
BBBStorage.setItem(USER_WAS_EJECTED, User.userId);
|
||||
}
|
||||
|
||||
let userSubscriptionHandler;
|
||||
|
||||
const codeError = Session.get('codeError');
|
||||
const { streams: usersVideo } = VideoService.getVideoStreams();
|
||||
|
||||
return {
|
||||
userWasEjected: (BBBStorage.getItem(USER_WAS_EJECTED) == connectionID),
|
||||
userWasEjected: (BBBStorage.getItem(USER_WAS_EJECTED)),
|
||||
approved,
|
||||
ejected,
|
||||
ejectedReason,
|
||||
|
@ -20,9 +20,9 @@ const propTypes = {
|
||||
children: PropTypes.element.isRequired,
|
||||
};
|
||||
|
||||
const DEFAULT_LANGUAGE = Meteor.settings.public.app.defaultSettings.application.fallbackLocale;
|
||||
const CLIENT_VERSION = Meteor.settings.public.app.html5ClientBuild;
|
||||
const FALLBACK_ON_EMPTY_STRING = Meteor.settings.public.app.fallbackOnEmptyLocaleString;
|
||||
const DEFAULT_LANGUAGE = window.meetingClientSettings.public.app.defaultSettings.application.fallbackLocale;
|
||||
const CLIENT_VERSION = window.meetingClientSettings.public.app.html5ClientBuild;
|
||||
const FALLBACK_ON_EMPTY_STRING = window.meetingClientSettings.public.app.fallbackOnEmptyLocaleString;
|
||||
|
||||
const RTL_LANGUAGES = ['ar', 'dv', 'fa', 'he'];
|
||||
const LARGE_FONT_LANGUAGES = ['te', 'km'];
|
||||
|
52
bigbluebutton-html5/imports/startup/client/intlAdapter.tsx
Normal file
52
bigbluebutton-html5/imports/startup/client/intlAdapter.tsx
Normal file
@ -0,0 +1,52 @@
|
||||
import React, { useContext, useEffect } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import Settings from '/imports/ui/services/settings';
|
||||
import { Session } from 'meteor/session';
|
||||
import { formatLocaleCode } from '/imports/utils/string-utils';
|
||||
import Intl from '/imports/ui/services/locale';
|
||||
import useCurrentLocale from '/imports/ui/core/local-states/useCurrentLocale';
|
||||
import { LoadingContext } from '/imports/ui/components/common/loading-screen/loading-screen-HOC/component';
|
||||
|
||||
const RTL_LANGUAGES = ['ar', 'dv', 'fa', 'he'];
|
||||
const LARGE_FONT_LANGUAGES = ['te', 'km'];
|
||||
|
||||
interface IntlAdapterProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const IntlAdapter: React.FC<IntlAdapterProps> = ({
|
||||
children,
|
||||
}) => {
|
||||
const [currentLocale] = useCurrentLocale();
|
||||
const intl = useIntl();
|
||||
const loadingContextInfo = useContext(LoadingContext);
|
||||
const setUp = () => {
|
||||
if (currentLocale) {
|
||||
const { language, formattedLocale } = formatLocaleCode(currentLocale);
|
||||
// @ts-ignore - JS code
|
||||
Settings.application.locale = currentLocale;
|
||||
Intl.setLocale(formattedLocale, intl.messages);
|
||||
if (RTL_LANGUAGES.includes(currentLocale.substring(0, 2))) {
|
||||
// @ts-ignore - JS code
|
||||
document.body.parentNode.setAttribute('dir', 'rtl');
|
||||
// @ts-ignore - JS code
|
||||
Settings.application.isRTL = true;
|
||||
} else {
|
||||
// @ts-ignore - JS code
|
||||
document.body.parentNode.setAttribute('dir', 'ltr');
|
||||
// @ts-ignore - JS code
|
||||
Settings.application.isRTL = false;
|
||||
}
|
||||
Session.set('isLargeFont', LARGE_FONT_LANGUAGES.includes(currentLocale.substring(0, 2)));
|
||||
document.getElementsByTagName('html')[0].lang = formattedLocale;
|
||||
document.body.classList.add(`lang-${language}`);
|
||||
Settings.save();
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(setUp, []);
|
||||
useEffect(setUp, [currentLocale]);
|
||||
return !loadingContextInfo.isLoading ? children : null;
|
||||
};
|
||||
|
||||
export default IntlAdapter;
|
151
bigbluebutton-html5/imports/startup/client/intlLoader.tsx
Normal file
151
bigbluebutton-html5/imports/startup/client/intlLoader.tsx
Normal file
@ -0,0 +1,151 @@
|
||||
import React, { useCallback, useContext, useEffect } from 'react';
|
||||
import { IntlProvider } from 'react-intl';
|
||||
import { LoadingContext } from '/imports/ui/components/common/loading-screen/loading-screen-HOC/component';
|
||||
import useCurrentLocale from '/imports/ui/core/local-states/useCurrentLocale';
|
||||
import logger from './logger';
|
||||
|
||||
interface LocaleEndpointResponse {
|
||||
defaultLocale: string;
|
||||
fallbackOnEmptyLocaleString: boolean;
|
||||
normalizedLocale: string;
|
||||
regionDefaultLocale: string;
|
||||
}
|
||||
|
||||
interface LocaleJson {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
interface IntlLoaderContainerProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
interface IntlLoaderProps extends IntlLoaderContainerProps {
|
||||
currentLocale: string;
|
||||
setCurrentLocale: (locale: string) => void;
|
||||
}
|
||||
|
||||
const buildFetchLocale = (locale: string) => {
|
||||
const localesPath = 'locales';
|
||||
return new Promise((resolve) => {
|
||||
fetch(`${localesPath}/${locale}.json`)
|
||||
.then((response) => {
|
||||
if (!response.ok) {
|
||||
return resolve(false);
|
||||
}
|
||||
return response.json()
|
||||
.then((jsonResponse) => resolve(jsonResponse))
|
||||
.catch(() => {
|
||||
logger.error({ logCode: 'intl_parse_locale_SyntaxError' }, `Could not parse locale file ${locale}.json, invalid json`);
|
||||
resolve(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const IntlLoader: React.FC<IntlLoaderProps> = ({
|
||||
children,
|
||||
currentLocale,
|
||||
setCurrentLocale,
|
||||
}) => {
|
||||
const loadingContextInfo = useContext(LoadingContext);
|
||||
|
||||
const [fetching, setFetching] = React.useState(false);
|
||||
const [normalizedLocale, setNormalizedLocale] = React.useState(navigator.language.replace('_', '-'));
|
||||
const [messages, setMessages] = React.useState<LocaleJson>({});
|
||||
const [fallbackOnEmptyLocaleString, setFallbackOnEmptyLocaleString] = React.useState(false);
|
||||
|
||||
const fetchLocalizedMessages = useCallback((locale: string, init: boolean) => {
|
||||
const url = `./locale?locale=${locale}&init=${init}`;
|
||||
setFetching(true);
|
||||
// fetch localized messages
|
||||
fetch(url)
|
||||
.then((response) => {
|
||||
if (!response.ok) {
|
||||
loadingContextInfo.setLoading(false, '');
|
||||
throw new Error('unable to fetch localized messages');
|
||||
}
|
||||
return response.json();
|
||||
}).then((data: LocaleEndpointResponse) => {
|
||||
const {
|
||||
defaultLocale,
|
||||
regionDefaultLocale,
|
||||
normalizedLocale,
|
||||
fallbackOnEmptyLocaleString: FOEL,
|
||||
} = data;
|
||||
setFallbackOnEmptyLocaleString(FOEL);
|
||||
const languageSets = Array.from(new Set([
|
||||
defaultLocale,
|
||||
regionDefaultLocale,
|
||||
normalizedLocale,
|
||||
])).filter((locale) => locale);
|
||||
|
||||
Promise.all(languageSets.map((locale) => buildFetchLocale(locale)))
|
||||
.then((resp) => {
|
||||
const typedResp = resp as Array<LocaleJson | boolean>;
|
||||
const foundLocales = typedResp.filter((locale) => locale instanceof Object) as LocaleJson[];
|
||||
if (foundLocales.length === 0) {
|
||||
const error = `${{ logCode: 'intl_fetch_locale_error' }},Could not fetch any locale file for ${languageSets.join(', ')}`;
|
||||
loadingContextInfo.setLoading(false, '');
|
||||
logger.error(error);
|
||||
throw new Error(error);
|
||||
}
|
||||
const mergedLocale = foundLocales
|
||||
.reduce((acc, locale: LocaleJson) => Object.assign(acc, locale), {});
|
||||
const replacedLocale = normalizedLocale.replace('_', '-');
|
||||
setFetching(false);
|
||||
setNormalizedLocale(replacedLocale);
|
||||
setCurrentLocale(replacedLocale);
|
||||
setMessages(mergedLocale);
|
||||
if (!init) {
|
||||
loadingContextInfo.setLoading(false, '');
|
||||
}
|
||||
}).catch((error) => {
|
||||
loadingContextInfo.setLoading(false, '');
|
||||
throw new Error(error);
|
||||
});
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const language = navigator.languages ? navigator.languages[0] : navigator.language;
|
||||
fetchLocalizedMessages(language, true);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (currentLocale !== normalizedLocale) {
|
||||
fetchLocalizedMessages(currentLocale, false);
|
||||
}
|
||||
}, [currentLocale]);
|
||||
|
||||
useEffect(() => {
|
||||
if (fetching) {
|
||||
loadingContextInfo.setLoading(true, 'Fetching locale');
|
||||
}
|
||||
}, [fetching]);
|
||||
|
||||
return !fetching || Object.keys(messages).length > 0 ? (
|
||||
<IntlProvider
|
||||
fallbackOnEmptyString={fallbackOnEmptyLocaleString}
|
||||
locale={normalizedLocale.replace('_', '-').replace('@', '-')}
|
||||
messages={messages}
|
||||
>
|
||||
{children}
|
||||
</IntlProvider>
|
||||
) : null;
|
||||
};
|
||||
|
||||
const IntlLoaderContainer: React.FC<IntlLoaderContainerProps> = ({
|
||||
children,
|
||||
}) => {
|
||||
const [currentLocale, setCurrentLocale] = useCurrentLocale();
|
||||
return (
|
||||
<IntlLoader
|
||||
currentLocale={currentLocale}
|
||||
setCurrentLocale={setCurrentLocale}
|
||||
>
|
||||
{children}
|
||||
</IntlLoader>
|
||||
);
|
||||
};
|
||||
|
||||
export default IntlLoaderContainer;
|
@ -1,4 +1,3 @@
|
||||
import Auth from '/imports/ui/services/auth';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { createLogger, stdSerializers } from 'browser-bunyan';
|
||||
import { ConsoleFormattedStream } from '@browser-bunyan/console-formatted-stream';
|
||||
@ -15,8 +14,43 @@ import { nameFromLevel } from '@browser-bunyan/levels';
|
||||
// "url": "","method": ""}
|
||||
// externalURL is the end-point that logs will be sent to
|
||||
// Call the logger by doing a function call with the level name, I.e, logger.warn('Hi on warn')
|
||||
const fallback = { console: { enabled: true, level: 'info' } };
|
||||
const LOG_CONFIG = (JSON.parse(sessionStorage.getItem('clientStartupSettings') || '{}')?.clientLog) || fallback;
|
||||
|
||||
const LOG_CONFIG = Meteor.settings.public.clientLog || { console: { enabled: true, level: 'info' } };
|
||||
export function createStreamForTarget(target, options) {
|
||||
const TARGET_EXTERNAL = 'external';
|
||||
const TARGET_CONSOLE = 'console';
|
||||
const TARGET_SERVER = 'server';
|
||||
|
||||
let Stream = ConsoleRawStream;
|
||||
switch (target) {
|
||||
case TARGET_EXTERNAL:
|
||||
Stream = ServerLoggerStream;
|
||||
break;
|
||||
case TARGET_CONSOLE:
|
||||
Stream = ConsoleFormattedStream;
|
||||
break;
|
||||
case TARGET_SERVER:
|
||||
Stream = MeteorStream;
|
||||
break;
|
||||
default:
|
||||
Stream = ConsoleFormattedStream;
|
||||
}
|
||||
|
||||
return new Stream(options);
|
||||
}
|
||||
|
||||
export function generateLoggerStreams(config) {
|
||||
let result = [];
|
||||
Object.keys(config).forEach((key) => {
|
||||
const logOption = config[key];
|
||||
if (logOption && logOption.enabled) {
|
||||
const { level, ...streamOptions } = logOption;
|
||||
result = result.concat({ level, stream: createStreamForTarget(key, streamOptions) });
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
// Custom stream that logs to an end-point
|
||||
class ServerLoggerStream extends ServerStream {
|
||||
@ -26,16 +60,56 @@ class ServerLoggerStream extends ServerStream {
|
||||
if (params.logTag) {
|
||||
this.logTagString = params.logTag;
|
||||
}
|
||||
this.auth = null;
|
||||
}
|
||||
|
||||
setConfig(config) {
|
||||
const streams = generateLoggerStreams(config);
|
||||
const { addStream } = this;
|
||||
streams.forEach((stream) => {
|
||||
addStream(stream);
|
||||
});
|
||||
}
|
||||
|
||||
setAuth(auth) {
|
||||
this.auth = auth;
|
||||
}
|
||||
|
||||
getUserData() {
|
||||
let userInfo = {};
|
||||
if (this.auth) {
|
||||
userInfo = this.auth.fullInfo;
|
||||
} else {
|
||||
userInfo = {
|
||||
meetingId: sessionStorage.getItem('meetingId'),
|
||||
userId: sessionStorage.getItem('userId'),
|
||||
logoutUrl: sessionStorage.getItem('logoutUrl'),
|
||||
sessionToken: sessionStorage.getItem('sessionToken'),
|
||||
userName: sessionStorage.getItem('userName'),
|
||||
extId: sessionStorage.getItem('extId'),
|
||||
meetingName: sessionStorage.getItem('meetingName'),
|
||||
};
|
||||
}
|
||||
|
||||
if (userInfo.meetingId) {
|
||||
userInfo = {
|
||||
sessionToken: sessionStorage.getItem('sessionToken'),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
fullInfo: userInfo,
|
||||
};
|
||||
}
|
||||
|
||||
write(rec) {
|
||||
const { fullInfo } = Auth;
|
||||
const { fullInfo } = this.getUserData();
|
||||
|
||||
this.rec = rec;
|
||||
if (fullInfo.meetingId != null) {
|
||||
this.rec.userInfo = fullInfo;
|
||||
}
|
||||
this.rec.clientBuild = Meteor.settings.public.app.html5ClientBuild;
|
||||
this.rec.clientBuild = window.meetingClientSettings.public.app.html5ClientBuild;
|
||||
this.rec.connectionId = Meteor.connection._lastSessionId;
|
||||
if (this.logTagString) {
|
||||
this.rec.logTag = this.logTagString;
|
||||
@ -47,7 +121,7 @@ class ServerLoggerStream extends ServerStream {
|
||||
// Custom stream to log to the meteor server
|
||||
class MeteorStream {
|
||||
write(rec) {
|
||||
const { fullInfo } = Auth;
|
||||
const { fullInfo } = this.getUserData();
|
||||
const clientURL = window.location.href;
|
||||
|
||||
this.rec = rec;
|
||||
@ -78,42 +152,6 @@ class MeteorStream {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function createStreamForTarget(target, options) {
|
||||
const TARGET_EXTERNAL = 'external';
|
||||
const TARGET_CONSOLE = 'console';
|
||||
const TARGET_SERVER = 'server';
|
||||
|
||||
let Stream = ConsoleRawStream;
|
||||
switch (target) {
|
||||
case TARGET_EXTERNAL:
|
||||
Stream = ServerLoggerStream;
|
||||
break;
|
||||
case TARGET_CONSOLE:
|
||||
Stream = ConsoleFormattedStream;
|
||||
break;
|
||||
case TARGET_SERVER:
|
||||
Stream = MeteorStream;
|
||||
break;
|
||||
default:
|
||||
Stream = ConsoleFormattedStream;
|
||||
}
|
||||
|
||||
return new Stream(options);
|
||||
}
|
||||
|
||||
function generateLoggerStreams(config) {
|
||||
let result = [];
|
||||
Object.keys(config).forEach((key) => {
|
||||
const logOption = config[key];
|
||||
if (logOption && logOption.enabled) {
|
||||
const { level, ...streamOptions } = logOption;
|
||||
result = result.concat({ level, stream: createStreamForTarget(key, streamOptions) });
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
// Creates the logger with the array of streams of the chosen targets
|
||||
const logger = createLogger({
|
||||
name: 'clientLogger',
|
||||
@ -122,5 +160,4 @@ const logger = createLogger({
|
||||
src: true,
|
||||
});
|
||||
|
||||
|
||||
export default logger;
|
||||
|
@ -13,6 +13,10 @@ import { PrometheusAgent, METRIC_NAMES } from './prom-metrics/index.js'
|
||||
|
||||
let guestWaitHtml = '';
|
||||
|
||||
const DEFAULT_LANGUAGE = Meteor.settings.public.app.defaultSettings.application.fallbackLocale;
|
||||
const CLIENT_VERSION = Meteor.settings.public.app.html5ClientBuild;
|
||||
const FALLBACK_ON_EMPTY_STRING = Meteor.settings.public.app.fallbackOnEmptyLocaleString;
|
||||
|
||||
const env = Meteor.isDevelopment ? 'development' : 'production';
|
||||
|
||||
const meteorRoot = fs.realpathSync(`${process.cwd()}/../`);
|
||||
@ -273,6 +277,8 @@ WebApp.connectHandlers.use('/locale', (req, res) => {
|
||||
res.end(JSON.stringify({
|
||||
normalizedLocale: localeFile,
|
||||
regionDefaultLocale: (regionDefault && regionDefault !== localeFile) ? regionDefault : '',
|
||||
defaultLocale: DEFAULT_LANGUAGE,
|
||||
fallbackOnEmptyLocaleString: FALLBACK_ON_EMPTY_STRING,
|
||||
}));
|
||||
});
|
||||
|
||||
|
846
bigbluebutton-html5/imports/ui/Types/meetingClientSettings.ts
Normal file
846
bigbluebutton-html5/imports/ui/Types/meetingClientSettings.ts
Normal file
@ -0,0 +1,846 @@
|
||||
export interface MeetingClientSettings {
|
||||
public: Public
|
||||
private: Private
|
||||
}
|
||||
|
||||
export interface Public {
|
||||
app: App
|
||||
externalVideoPlayer: ExternalVideoPlayer
|
||||
kurento: Kurento
|
||||
syncUsersWithConnectionManager: SyncUsersWithConnectionManager
|
||||
poll: Poll
|
||||
captions: Captions
|
||||
timer: Timer
|
||||
chat: Chat
|
||||
userReaction: UserReaction
|
||||
userStatus: UserStatus
|
||||
notes: Notes
|
||||
layout: Layout
|
||||
pads: Pads
|
||||
media: Media
|
||||
stats: Stats
|
||||
presentation: Presentation
|
||||
selectRandomUser: SelectRandomUser
|
||||
user: User
|
||||
whiteboard: Whiteboard
|
||||
clientLog: ClientLog
|
||||
virtualBackgrounds: VirtualBackgrounds
|
||||
}
|
||||
|
||||
export interface App {
|
||||
mobileFontSize: string
|
||||
desktopFontSize: string
|
||||
audioChatNotification: boolean
|
||||
autoJoin: boolean
|
||||
listenOnlyMode: boolean
|
||||
forceListenOnly: boolean
|
||||
skipCheck: boolean
|
||||
skipCheckOnJoin: boolean
|
||||
enableDynamicAudioDeviceSelection: boolean
|
||||
clientTitle: string
|
||||
appName: string
|
||||
bbbServerVersion: string
|
||||
displayBbbServerVersion: boolean
|
||||
copyright: string
|
||||
html5ClientBuild: string
|
||||
helpLink: string
|
||||
delayForUnmountOfSharedNote: number
|
||||
bbbTabletApp: BbbTabletApp
|
||||
lockOnJoin: boolean
|
||||
cdn: string
|
||||
basename: string
|
||||
bbbWebBase: string
|
||||
learningDashboardBase: string
|
||||
customStyleUrl: string | null
|
||||
darkTheme: DarkTheme
|
||||
askForFeedbackOnLogout: boolean
|
||||
askForConfirmationOnLeave: boolean
|
||||
wakeLock: WakeLock
|
||||
allowDefaultLogoutUrl: boolean
|
||||
allowUserLookup: boolean
|
||||
dynamicGuestPolicy: boolean
|
||||
enableGuestLobbyMessage: boolean
|
||||
guestPolicyExtraAllowOptions: boolean
|
||||
alwaysShowWaitingRoomUI: boolean
|
||||
enableLimitOfViewersInWebcam: boolean
|
||||
enableMultipleCameras: boolean
|
||||
enableCameraAsContent: boolean
|
||||
enableWebcamSelectorButton: boolean
|
||||
enableTalkingIndicator: boolean
|
||||
enableCameraBrightness: boolean
|
||||
mirrorOwnWebcam: boolean
|
||||
viewersInWebcam: number
|
||||
ipv4FallbackDomain: string
|
||||
allowLogout: boolean
|
||||
allowFullscreen: boolean
|
||||
preloadNextSlides: number
|
||||
warnAboutUnsavedContentOnMeetingEnd: boolean
|
||||
audioCaptions: AudioCaptions
|
||||
mutedAlert: MutedAlert
|
||||
remainingTimeThreshold: number
|
||||
remainingTimeAlertThresholdArray: number[]
|
||||
enableDebugWindow: boolean
|
||||
breakouts: Breakouts
|
||||
customHeartbeat: boolean
|
||||
showAllAvailableLocales: boolean
|
||||
showAudioFilters: boolean
|
||||
raiseHandActionButton: RaiseHandActionButton
|
||||
reactionsButton: ReactionsButton
|
||||
emojiRain: EmojiRain
|
||||
enableNetworkStats: boolean
|
||||
enableCopyNetworkStatsButton: boolean
|
||||
userSettingsStorage: string
|
||||
defaultSettings: DefaultSettings
|
||||
shortcuts: Shortcuts
|
||||
branding: Branding
|
||||
connectionTimeout: number
|
||||
showHelpButton: boolean
|
||||
effectiveConnection: string[]
|
||||
fallbackOnEmptyLocaleString: boolean
|
||||
disableWebsocketFallback: boolean
|
||||
}
|
||||
|
||||
export interface BbbTabletApp {
|
||||
enabled: boolean
|
||||
iosAppStoreUrl: string
|
||||
iosAppUrlScheme: string
|
||||
}
|
||||
|
||||
export interface DarkTheme {
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
export interface WakeLock {
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
export interface AudioCaptions {
|
||||
enabled: boolean
|
||||
mobile: boolean
|
||||
provider: string
|
||||
language: Language
|
||||
}
|
||||
|
||||
export interface Language {
|
||||
available: string[]
|
||||
forceLocale: boolean
|
||||
defaultSelectLocale: boolean
|
||||
locale: string
|
||||
}
|
||||
|
||||
export interface MutedAlert {
|
||||
enabled: boolean
|
||||
interval: number
|
||||
threshold: number
|
||||
duration: number
|
||||
}
|
||||
|
||||
export interface Breakouts {
|
||||
allowUserChooseRoomByDefault: boolean
|
||||
captureWhiteboardByDefault: boolean
|
||||
captureSharedNotesByDefault: boolean
|
||||
sendInvitationToAssignedModeratorsByDefault: boolean
|
||||
breakoutRoomLimit: number
|
||||
}
|
||||
|
||||
export interface RaiseHandActionButton {
|
||||
enabled: boolean
|
||||
centered: boolean
|
||||
}
|
||||
|
||||
export interface ReactionsButton {
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
export interface EmojiRain {
|
||||
enabled: boolean
|
||||
intervalEmojis: number
|
||||
numberOfEmojis: number
|
||||
emojiSize: number
|
||||
}
|
||||
|
||||
export interface DefaultSettings {
|
||||
application: Application
|
||||
audio: Audio
|
||||
dataSaving: DataSaving
|
||||
}
|
||||
|
||||
export interface Application {
|
||||
selectedLayout: string
|
||||
animations: boolean
|
||||
chatAudioAlerts: boolean
|
||||
chatPushAlerts: boolean
|
||||
userJoinAudioAlerts: boolean
|
||||
userJoinPushAlerts: boolean
|
||||
userLeaveAudioAlerts: boolean
|
||||
userLeavePushAlerts: boolean
|
||||
raiseHandAudioAlerts: boolean
|
||||
raiseHandPushAlerts: boolean
|
||||
guestWaitingAudioAlerts: boolean
|
||||
guestWaitingPushAlerts: boolean
|
||||
wakeLock: boolean
|
||||
paginationEnabled: boolean
|
||||
whiteboardToolbarAutoHide: boolean
|
||||
autoCloseReactionsBar: boolean
|
||||
darkTheme: boolean
|
||||
fallbackLocale: string
|
||||
overrideLocale: string | null
|
||||
}
|
||||
|
||||
export interface Audio {
|
||||
inputDeviceId: string
|
||||
outputDeviceId: string
|
||||
}
|
||||
|
||||
export interface DataSaving {
|
||||
viewParticipantsWebcams: boolean
|
||||
viewScreenshare: boolean
|
||||
}
|
||||
|
||||
export interface Shortcuts {
|
||||
openOptions: OpenOptions
|
||||
toggleUserList: ToggleUserList
|
||||
toggleMute: ToggleMute
|
||||
joinAudio: JoinAudio
|
||||
leaveAudio: LeaveAudio
|
||||
togglePublicChat: TogglePublicChat
|
||||
hidePrivateChat: HidePrivateChat
|
||||
closePrivateChat: ClosePrivateChat
|
||||
raiseHand: RaiseHand
|
||||
openActions: OpenActions
|
||||
openDebugWindow: OpenDebugWindow
|
||||
}
|
||||
|
||||
export interface OpenOptions {
|
||||
accesskey: string
|
||||
descId: string
|
||||
}
|
||||
|
||||
export interface ToggleUserList {
|
||||
accesskey: string
|
||||
descId: string
|
||||
}
|
||||
|
||||
export interface ToggleMute {
|
||||
accesskey: string
|
||||
descId: string
|
||||
}
|
||||
|
||||
export interface JoinAudio {
|
||||
accesskey: string
|
||||
descId: string
|
||||
}
|
||||
|
||||
export interface LeaveAudio {
|
||||
accesskey: string
|
||||
descId: string
|
||||
}
|
||||
|
||||
export interface TogglePublicChat {
|
||||
accesskey: string
|
||||
descId: string
|
||||
}
|
||||
|
||||
export interface HidePrivateChat {
|
||||
accesskey: string
|
||||
descId: string
|
||||
}
|
||||
|
||||
export interface ClosePrivateChat {
|
||||
accesskey: string
|
||||
descId: string
|
||||
}
|
||||
|
||||
export interface RaiseHand {
|
||||
accesskey: string
|
||||
descId: string
|
||||
}
|
||||
|
||||
export interface OpenActions {
|
||||
accesskey: string
|
||||
descId: string
|
||||
}
|
||||
|
||||
export interface OpenDebugWindow {
|
||||
accesskey: string
|
||||
descId: string
|
||||
}
|
||||
|
||||
export interface Branding {
|
||||
displayBrandingArea: boolean
|
||||
}
|
||||
|
||||
export interface ExternalVideoPlayer {
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
export interface Kurento {
|
||||
wsUrl: string
|
||||
cameraWsOptions: CameraWsOptions
|
||||
gUMTimeout: number
|
||||
signalCandidates: boolean
|
||||
traceLogs: boolean
|
||||
cameraTimeouts: CameraTimeouts
|
||||
screenshare: Screenshare
|
||||
cameraProfiles: CameraProfile[]
|
||||
enableScreensharing: boolean
|
||||
enableVideo: boolean
|
||||
enableVideoMenu: boolean
|
||||
enableVideoPin: boolean
|
||||
autoShareWebcam: boolean
|
||||
skipVideoPreview: boolean
|
||||
skipVideoPreviewOnFirstJoin: boolean
|
||||
cameraSortingModes: CameraSortingModes
|
||||
cameraQualityThresholds: CameraQualityThresholds
|
||||
pagination: Pagination
|
||||
paginationThresholds: PaginationThresholds
|
||||
}
|
||||
|
||||
export interface CameraWsOptions {
|
||||
wsConnectionTimeout: number
|
||||
maxRetries: number
|
||||
debug: boolean
|
||||
heartbeat: Heartbeat
|
||||
}
|
||||
|
||||
export interface Heartbeat {
|
||||
interval: number
|
||||
delay: number
|
||||
reconnectOnFailure: boolean
|
||||
}
|
||||
|
||||
export interface CameraTimeouts {
|
||||
baseTimeout: number
|
||||
maxTimeout: number
|
||||
}
|
||||
|
||||
export interface Screenshare {
|
||||
enableVolumeControl: boolean
|
||||
subscriberOffering: boolean
|
||||
bitrate: number
|
||||
mediaTimeouts: MediaTimeouts
|
||||
constraints: Constraints
|
||||
}
|
||||
|
||||
export interface MediaTimeouts {
|
||||
maxConnectionAttempts: number
|
||||
baseTimeout: number
|
||||
baseReconnectionTimeout: number
|
||||
maxTimeout: number
|
||||
timeoutIncreaseFactor: number
|
||||
}
|
||||
|
||||
export interface Constraints {
|
||||
video: Video
|
||||
audio: boolean
|
||||
}
|
||||
|
||||
export interface Video {
|
||||
frameRate: FrameRate
|
||||
width: Width
|
||||
height: Height
|
||||
}
|
||||
|
||||
export interface FrameRate {
|
||||
ideal: number
|
||||
max: number
|
||||
}
|
||||
|
||||
export interface Width {
|
||||
max: number
|
||||
}
|
||||
|
||||
export interface Height {
|
||||
max: number
|
||||
}
|
||||
|
||||
export interface CameraProfile {
|
||||
id: string
|
||||
name: string
|
||||
bitrate: number
|
||||
hidden?: boolean
|
||||
default?: boolean
|
||||
constraints?: Constraints2
|
||||
}
|
||||
|
||||
export interface Constraints2 {
|
||||
width: number
|
||||
height: number
|
||||
frameRate?: number
|
||||
}
|
||||
|
||||
export interface CameraSortingModes {
|
||||
defaultSorting: string
|
||||
paginationSorting: string
|
||||
}
|
||||
|
||||
export interface CameraQualityThresholds {
|
||||
enabled: boolean
|
||||
applyConstraints: boolean
|
||||
privilegedStreams: boolean
|
||||
debounceTime: number
|
||||
thresholds: Threshold[]
|
||||
}
|
||||
|
||||
export interface Threshold {
|
||||
threshold: number
|
||||
profile: string
|
||||
}
|
||||
|
||||
export interface Pagination {
|
||||
paginationToggleEnabled: boolean
|
||||
pageChangeDebounceTime: number
|
||||
desktopPageSizes: DesktopPageSizes
|
||||
mobilePageSizes: MobilePageSizes
|
||||
desktopGridSizes: DesktopGridSizes
|
||||
mobileGridSizes: MobileGridSizes
|
||||
}
|
||||
|
||||
export interface DesktopPageSizes {
|
||||
moderator: number
|
||||
viewer: number
|
||||
}
|
||||
|
||||
export interface MobilePageSizes {
|
||||
moderator: number
|
||||
viewer: number
|
||||
}
|
||||
|
||||
export interface DesktopGridSizes {
|
||||
moderator: number
|
||||
viewer: number
|
||||
}
|
||||
|
||||
export interface MobileGridSizes {
|
||||
moderator: number
|
||||
viewer: number
|
||||
}
|
||||
|
||||
export interface PaginationThresholds {
|
||||
enabled: boolean
|
||||
thresholds: Threshold2[]
|
||||
}
|
||||
|
||||
export interface Threshold2 {
|
||||
users: number
|
||||
desktopPageSizes: DesktopPageSizes2
|
||||
}
|
||||
|
||||
export interface DesktopPageSizes2 {
|
||||
moderator: number
|
||||
viewer: number
|
||||
}
|
||||
|
||||
export interface SyncUsersWithConnectionManager {
|
||||
enabled: boolean
|
||||
syncInterval: number
|
||||
}
|
||||
|
||||
export interface Poll {
|
||||
enabled: boolean
|
||||
allowCustomResponseInput: boolean
|
||||
maxCustom: number
|
||||
maxTypedAnswerLength: number
|
||||
chatMessage: boolean
|
||||
}
|
||||
|
||||
export interface Captions {
|
||||
enabled: boolean
|
||||
id: string
|
||||
dictation: boolean
|
||||
background: string
|
||||
font: Font
|
||||
lines: number
|
||||
time: number
|
||||
}
|
||||
|
||||
export interface Font {
|
||||
color: string
|
||||
family: string
|
||||
size: string
|
||||
}
|
||||
|
||||
export interface Timer {
|
||||
enabled: boolean
|
||||
alarm: boolean
|
||||
music: Music
|
||||
interval: Interval
|
||||
time: number
|
||||
tabIndicator: boolean
|
||||
}
|
||||
|
||||
export interface Music {
|
||||
enabled: boolean
|
||||
volume: number
|
||||
track1: string
|
||||
track2: string
|
||||
track3: string
|
||||
}
|
||||
|
||||
export interface Interval {
|
||||
clock: number
|
||||
offset: number
|
||||
}
|
||||
|
||||
export interface Chat {
|
||||
enabled: boolean
|
||||
itemsPerPage: number
|
||||
timeBetweenFetchs: number
|
||||
enableSaveAndCopyPublicChat: boolean
|
||||
bufferChatInsertsMs: number
|
||||
startClosed: boolean
|
||||
min_message_length: number
|
||||
max_message_length: number
|
||||
grouping_messages_window: number
|
||||
type_system: string
|
||||
type_public: string
|
||||
type_private: string
|
||||
system_userid: string
|
||||
system_username: string
|
||||
public_id: string
|
||||
public_group_id: string
|
||||
public_userid: string
|
||||
public_username: string
|
||||
storage_key: string
|
||||
system_messages_keys: SystemMessagesKeys
|
||||
typingIndicator: TypingIndicator
|
||||
moderatorChatEmphasized: boolean
|
||||
autoConvertEmoji: boolean
|
||||
emojiPicker: EmojiPicker
|
||||
disableEmojis: string[]
|
||||
allowedElements: string[]
|
||||
}
|
||||
|
||||
export interface SystemMessagesKeys {
|
||||
chat_clear: string
|
||||
chat_poll_result: string
|
||||
chat_exported_presentation: string
|
||||
chat_status_message: string
|
||||
}
|
||||
|
||||
export interface TypingIndicator {
|
||||
enabled: boolean
|
||||
showNames: boolean
|
||||
}
|
||||
|
||||
export interface EmojiPicker {
|
||||
enable: boolean
|
||||
}
|
||||
|
||||
export interface UserReaction {
|
||||
enabled: boolean
|
||||
expire: number
|
||||
reactions: Reaction[]
|
||||
}
|
||||
|
||||
export interface Reaction {
|
||||
id: string
|
||||
native: string
|
||||
}
|
||||
|
||||
export interface UserStatus {
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
export interface Notes {
|
||||
enabled: boolean
|
||||
id: string
|
||||
pinnable: boolean
|
||||
}
|
||||
|
||||
export interface Layout {
|
||||
hidePresentationOnJoin: boolean
|
||||
showParticipantsOnLogin: boolean
|
||||
showPushLayoutButton: boolean
|
||||
showPushLayoutToggle: boolean
|
||||
}
|
||||
|
||||
export interface Pads {
|
||||
url: string
|
||||
cookie: Cookie
|
||||
}
|
||||
|
||||
export interface Cookie {
|
||||
path: string
|
||||
sameSite: string
|
||||
secure: boolean
|
||||
}
|
||||
|
||||
export interface Media {
|
||||
audio: Audio2
|
||||
stunTurnServersFetchAddress: string
|
||||
cacheStunTurnServers: boolean
|
||||
fallbackStunServer: string
|
||||
forceRelay: boolean
|
||||
forceRelayOnFirefox: boolean
|
||||
mediaTag: string
|
||||
callTransferTimeout: number
|
||||
callHangupTimeout: number
|
||||
callHangupMaximumRetries: number
|
||||
echoTestNumber: string
|
||||
listenOnlyCallTimeout: number
|
||||
transparentListenOnly: boolean
|
||||
fullAudioOffering: boolean
|
||||
listenOnlyOffering: boolean
|
||||
iceGatheringTimeout: number
|
||||
audioConnectionTimeout: number
|
||||
audioReconnectionDelay: number
|
||||
audioReconnectionAttempts: number
|
||||
sipjsHackViaWs: boolean
|
||||
sipjsAllowMdns: boolean
|
||||
sip_ws_host: string
|
||||
toggleMuteThrottleTime: number
|
||||
websocketKeepAliveInterval: number
|
||||
websocketKeepAliveDebounce: number
|
||||
traceSip: boolean
|
||||
sdpSemantics: string
|
||||
localEchoTest: LocalEchoTest
|
||||
showVolumeMeter: boolean
|
||||
}
|
||||
|
||||
export interface Audio2 {
|
||||
defaultFullAudioBridge: string
|
||||
defaultListenOnlyBridge: string
|
||||
bridges: Bridge[]
|
||||
retryThroughRelay: boolean
|
||||
}
|
||||
|
||||
export interface Bridge {
|
||||
name: string
|
||||
path: string
|
||||
}
|
||||
|
||||
export interface LocalEchoTest {
|
||||
enabled: boolean
|
||||
initialHearingState: boolean
|
||||
useRtcLoopbackInChromium: boolean
|
||||
delay: Delay
|
||||
}
|
||||
|
||||
export interface Delay {
|
||||
enabled: boolean
|
||||
delayTime: number
|
||||
maxDelayTime: number
|
||||
}
|
||||
|
||||
export interface Stats {
|
||||
enabled: boolean
|
||||
interval: number
|
||||
timeout: number
|
||||
log: boolean
|
||||
notification: Notification
|
||||
jitter: number[]
|
||||
loss: number[]
|
||||
rtt: number[]
|
||||
level: string[]
|
||||
help: string
|
||||
}
|
||||
|
||||
export interface Notification {
|
||||
warning: boolean
|
||||
error: boolean
|
||||
}
|
||||
|
||||
export interface Presentation {
|
||||
allowDownloadOriginal: boolean
|
||||
allowDownloadWithAnnotations: boolean
|
||||
allowSnapshotOfCurrentSlide: boolean
|
||||
panZoomThrottle: number
|
||||
restoreOnUpdate: boolean
|
||||
uploadEndpoint: string
|
||||
fileUploadConstraintsHint: boolean
|
||||
mirroredFromBBBCore: MirroredFromBbbcore
|
||||
uploadValidMimeTypes: UploadValidMimeType[]
|
||||
}
|
||||
|
||||
export interface MirroredFromBbbcore {
|
||||
uploadSizeMax: number
|
||||
uploadPagesMax: number
|
||||
}
|
||||
|
||||
export interface UploadValidMimeType {
|
||||
extension: string
|
||||
mime: string
|
||||
}
|
||||
|
||||
export interface SelectRandomUser {
|
||||
enabled: boolean
|
||||
countdown: boolean
|
||||
}
|
||||
|
||||
export interface User {
|
||||
role_moderator: string
|
||||
role_viewer: string
|
||||
label: Label
|
||||
}
|
||||
|
||||
export interface Label {
|
||||
moderator: boolean
|
||||
mobile: boolean
|
||||
guest: boolean
|
||||
sharingWebcam: boolean
|
||||
}
|
||||
|
||||
export interface Whiteboard {
|
||||
annotationsQueueProcessInterval: number
|
||||
cursorInterval: number
|
||||
pointerDiameter: number
|
||||
maxStickyNoteLength: number
|
||||
maxNumberOfAnnotations: number
|
||||
annotations: Annotations
|
||||
styles: Styles
|
||||
toolbar: Toolbar
|
||||
}
|
||||
|
||||
export interface Annotations {
|
||||
status: Status
|
||||
}
|
||||
|
||||
export interface Status {
|
||||
start: string
|
||||
update: string
|
||||
end: string
|
||||
}
|
||||
|
||||
export interface Styles {
|
||||
text: Text
|
||||
}
|
||||
|
||||
export interface Text {
|
||||
family: string
|
||||
}
|
||||
|
||||
export interface Toolbar {
|
||||
multiUserPenOnly: boolean
|
||||
colors: Color[]
|
||||
thickness: Thickness[]
|
||||
font_sizes: FontSize[]
|
||||
tools: Tool[]
|
||||
presenterTools: string[]
|
||||
multiUserTools: string[]
|
||||
}
|
||||
|
||||
export interface Color {
|
||||
label: string
|
||||
value: string
|
||||
}
|
||||
|
||||
export interface Thickness {
|
||||
value: number
|
||||
}
|
||||
|
||||
export interface FontSize {
|
||||
value: number
|
||||
}
|
||||
|
||||
export interface Tool {
|
||||
icon: string
|
||||
value: string
|
||||
}
|
||||
|
||||
export interface ClientLog {
|
||||
server: Server
|
||||
console: Console
|
||||
external: External
|
||||
}
|
||||
|
||||
export interface Server {
|
||||
enabled: boolean
|
||||
level: string
|
||||
}
|
||||
|
||||
export interface Console {
|
||||
enabled: boolean
|
||||
level: string
|
||||
}
|
||||
|
||||
export interface External {
|
||||
enabled: boolean
|
||||
level: string
|
||||
url: string
|
||||
method: string
|
||||
throttleInterval: number
|
||||
flushOnClose: boolean
|
||||
logTag: string
|
||||
}
|
||||
|
||||
export interface VirtualBackgrounds {
|
||||
enabled: boolean
|
||||
enableVirtualBackgroundUpload: boolean
|
||||
storedOnBBB: boolean
|
||||
showThumbnails: boolean
|
||||
imagesPath: string
|
||||
thumbnailsPath: string
|
||||
fileNames: string[]
|
||||
}
|
||||
|
||||
export interface Private {
|
||||
analytics: Analytics
|
||||
app: App2
|
||||
redis: Redis
|
||||
serverLog: ServerLog
|
||||
minBrowserVersions: MinBrowserVersion[]
|
||||
prometheus: Prometheus
|
||||
}
|
||||
|
||||
export interface Analytics {
|
||||
includeChat: boolean
|
||||
}
|
||||
|
||||
export interface App2 {
|
||||
host: string
|
||||
localesUrl: string
|
||||
pencilChunkLength: number
|
||||
loadSlidesFromHttpAlways: boolean
|
||||
}
|
||||
|
||||
export interface Redis {
|
||||
host: string
|
||||
port: string
|
||||
timeout: number
|
||||
password: string | null
|
||||
debug: boolean
|
||||
metrics: Metrics
|
||||
channels: Channels
|
||||
subscribeTo: string[]
|
||||
async: string[]
|
||||
ignored: string[]
|
||||
}
|
||||
|
||||
export interface Metrics {
|
||||
queueMetrics: boolean
|
||||
metricsDumpIntervalMs: number
|
||||
metricsFolderPath: string
|
||||
removeMeetingOnEnd: boolean
|
||||
}
|
||||
|
||||
export interface Channels {
|
||||
toAkkaApps: string
|
||||
toThirdParty: string
|
||||
}
|
||||
|
||||
export interface ServerLog {
|
||||
level: string
|
||||
streamerLog: boolean
|
||||
includeServerInfo: boolean
|
||||
healthChecker: HealthChecker
|
||||
}
|
||||
|
||||
export interface HealthChecker {
|
||||
enable: boolean
|
||||
intervalMs: number
|
||||
}
|
||||
|
||||
export interface MinBrowserVersion {
|
||||
browser: string
|
||||
version: number | number[] | string
|
||||
}
|
||||
|
||||
export interface Prometheus {
|
||||
enabled: boolean
|
||||
path: string
|
||||
collectDefaultMetrics: boolean
|
||||
collectRedisMetrics: boolean
|
||||
}
|
||||
|
||||
export default MeetingClientSettings;
|
@ -14,7 +14,7 @@ const AboutContainer = (props) => {
|
||||
|
||||
const getClientBuildInfo = () => (
|
||||
{
|
||||
settings: Meteor.settings.public.app,
|
||||
settings: window.meetingClientSettings.public.app,
|
||||
|
||||
}
|
||||
);
|
||||
|
@ -77,13 +77,13 @@ const ActionsBarContainer = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
const SELECT_RANDOM_USER_ENABLED = Meteor.settings.public.selectRandomUser.enabled;
|
||||
const RAISE_HAND_BUTTON_ENABLED = Meteor.settings.public.app.raiseHandActionButton.enabled;
|
||||
const RAISE_HAND_BUTTON_CENTERED = Meteor.settings.public.app.raiseHandActionButton.centered;
|
||||
const SELECT_RANDOM_USER_ENABLED = window.meetingClientSettings.public.selectRandomUser.enabled;
|
||||
const RAISE_HAND_BUTTON_ENABLED = window.meetingClientSettings.public.app.raiseHandActionButton.enabled;
|
||||
const RAISE_HAND_BUTTON_CENTERED = window.meetingClientSettings.public.app.raiseHandActionButton.centered;
|
||||
|
||||
const isReactionsButtonEnabled = () => {
|
||||
const USER_REACTIONS_ENABLED = Meteor.settings.public.userReaction.enabled;
|
||||
const REACTIONS_BUTTON_ENABLED = Meteor.settings.public.app.reactionsButton.enabled;
|
||||
const USER_REACTIONS_ENABLED = window.meetingClientSettings.public.userReaction.enabled;
|
||||
const REACTIONS_BUTTON_ENABLED = window.meetingClientSettings.public.app.reactionsButton.enabled;
|
||||
|
||||
return USER_REACTIONS_ENABLED && REACTIONS_BUTTON_ENABLED;
|
||||
};
|
||||
|
@ -7,7 +7,7 @@ import { PANELS, ACTIONS } from '../../layout/enums';
|
||||
import { uniqueId, safeMatch } from '/imports/utils/string-utils';
|
||||
import PollService from '/imports/ui/components/poll/service';
|
||||
|
||||
const POLL_SETTINGS = Meteor.settings.public.poll;
|
||||
const POLL_SETTINGS = window.meetingClientSettings.public.poll;
|
||||
const MAX_CUSTOM_FIELDS = POLL_SETTINGS.maxCustom;
|
||||
const MAX_CHAR_LIMIT = POLL_SETTINGS.maxTypedAnswerLength;
|
||||
const CANCELED_POLL_DELAY = 250;
|
||||
|
@ -11,7 +11,7 @@ import { useMutation } from '@apollo/client';
|
||||
|
||||
import Styled from './styles';
|
||||
|
||||
const REACTIONS = Meteor.settings.public.userReaction.reactions;
|
||||
const REACTIONS = window.meetingClientSettings.public.userReaction.reactions;
|
||||
|
||||
const ReactionsButton = (props) => {
|
||||
const {
|
||||
|
@ -81,7 +81,7 @@ class ActivityCheck extends Component {
|
||||
}
|
||||
|
||||
playAudioAlert() {
|
||||
this.alert = new Audio(`${Meteor.settings.public.app.cdn + Meteor.settings.public.app.basename + Meteor.settings.public.app.instanceId}/resources/sounds/notify.mp3`);
|
||||
this.alert = new Audio(`${window.meetingClientSettings.public.app.cdn + window.meetingClientSettings.public.app.basename + window.meetingClientSettings.public.app.instanceId}/resources/sounds/notify.mp3`);
|
||||
this.alert.addEventListener('ended', () => { this.alert.src = null; });
|
||||
this.alert.play();
|
||||
}
|
||||
|
@ -57,11 +57,11 @@ import FloatingWindowContainer from '/imports/ui/components/floating-window/cont
|
||||
import ChatAlertContainerGraphql from '../chat/chat-graphql/alert/component';
|
||||
|
||||
const MOBILE_MEDIA = 'only screen and (max-width: 40em)';
|
||||
const APP_CONFIG = Meteor.settings.public.app;
|
||||
const APP_CONFIG = window.meetingClientSettings.public.app;
|
||||
const DESKTOP_FONT_SIZE = APP_CONFIG.desktopFontSize;
|
||||
const MOBILE_FONT_SIZE = APP_CONFIG.mobileFontSize;
|
||||
const LAYOUT_CONFIG = Meteor.settings.public.layout;
|
||||
const CONFIRMATION_ON_LEAVE = Meteor.settings.public.app.askForConfirmationOnLeave;
|
||||
const LAYOUT_CONFIG = window.meetingClientSettings.public.layout;
|
||||
const CONFIRMATION_ON_LEAVE = window.meetingClientSettings.public.app.askForConfirmationOnLeave;
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
userListLabel: {
|
||||
|
@ -37,7 +37,7 @@ import App from './component';
|
||||
import useToggleVoice from '../audio/audio-graphql/hooks/useToggleVoice';
|
||||
import useUserChangedLocalSettings from '../../services/settings/hooks/useUserChangedLocalSettings';
|
||||
|
||||
const CUSTOM_STYLE_URL = Meteor.settings.public.app.customStyleUrl;
|
||||
const CUSTOM_STYLE_URL = window.meetingClientSettings.public.app.customStyleUrl;
|
||||
|
||||
const endMeeting = (code, ejectedReason) => {
|
||||
Session.set('codeError', code);
|
||||
@ -339,7 +339,7 @@ export default withTracker(() => {
|
||||
customStyleUrl = CUSTOM_STYLE_URL;
|
||||
}
|
||||
|
||||
const LAYOUT_CONFIG = Meteor.settings.public.layout;
|
||||
const LAYOUT_CONFIG = window.meetingClientSettings.public.layout;
|
||||
|
||||
return {
|
||||
captions: CaptionsService.isCaptionsActive() ? <CaptionsContainer /> : null,
|
||||
@ -376,7 +376,7 @@ export default withTracker(() => {
|
||||
isLargeFont: Session.get('isLargeFont'),
|
||||
presentationRestoreOnUpdate: getFromUserSettings(
|
||||
'bbb_force_restore_presentation_on_new_events',
|
||||
Meteor.settings.public.presentation.restoreOnUpdate,
|
||||
window.meetingClientSettings.public.presentation.restoreOnUpdate,
|
||||
),
|
||||
hidePresentationOnJoin: getFromUserSettings('bbb_hide_presentation_on_join', LAYOUT_CONFIG.hidePresentationOnJoin),
|
||||
hideActionsBar: getFromUserSettings('bbb_hide_actions_bar', false),
|
||||
|
@ -21,9 +21,9 @@ import ListenOnly from './buttons/listenOnly';
|
||||
import LiveSelection from './buttons/LiveSelection';
|
||||
|
||||
// @ts-ignore - temporary, while meteor exists in the project
|
||||
const { enableDynamicAudioDeviceSelection } = Meteor.settings.public.app;
|
||||
const { enableDynamicAudioDeviceSelection } = window.meetingClientSettings.public.app;
|
||||
// @ts-ignore - temporary, while meteor exists in the project
|
||||
const MUTE_ALERT_CONFIG = Meteor.settings.public.app.mutedAlert;
|
||||
const MUTE_ALERT_CONFIG = window.meetingClientSettings.public.app.mutedAlert;
|
||||
|
||||
// @ts-ignore - temporary while settings are still in .js
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
|
@ -7,9 +7,9 @@ import AudioManager from '/imports/ui/services/audio-manager';
|
||||
|
||||
const MUTED_KEY = 'muted';
|
||||
// @ts-ignore - temporary, while meteor exists in the project
|
||||
const APP_CONFIG = Meteor.settings.public.app;
|
||||
const APP_CONFIG = window.meetingClientSettings.public.app;
|
||||
// @ts-ignore - temporary, while meteor exists in the project
|
||||
const TOGGLE_MUTE_THROTTLE_TIME = Meteor.settings.public.media.toggleMuteThrottleTime;
|
||||
const TOGGLE_MUTE_THROTTLE_TIME = window.meetingClientSettings.public.media.toggleMuteThrottleTime;
|
||||
const DEVICE_LABEL_MAX_LENGTH = 40;
|
||||
const CLIENT_DID_USER_SELECTED_MICROPHONE_KEY = 'clientUserSelectedMicrophone';
|
||||
const CLIENT_DID_USER_SELECTED_LISTEN_ONLY_KEY = 'clientUserSelectedListenOnly';
|
||||
|
@ -19,7 +19,7 @@ import Service from '../service';
|
||||
|
||||
const AudioModalContainer = (props) => <AudioModal {...props} />;
|
||||
|
||||
const APP_CONFIG = Meteor.settings.public.app;
|
||||
const APP_CONFIG = window.meetingClientSettings.public.app;
|
||||
|
||||
const invalidDialNumbers = ['0', '613-555-1212', '613-555-1234', '0000'];
|
||||
const isRTL = document.documentElement.getAttribute('dir') === 'rtl';
|
||||
|
@ -8,7 +8,7 @@ const AudioTestContainer = (props) => <AudioTest {...props} />;
|
||||
export default withTracker(() => ({
|
||||
outputDeviceId: Service.outputDeviceId(),
|
||||
handlePlayAudioSample: (deviceId) => {
|
||||
const sound = new Audio(`${Meteor.settings.public.app.cdn + Meteor.settings.public.app.basename + Meteor.settings.public.app.instanceId}/resources/sounds/audioSample.mp3`);
|
||||
const sound = new Audio(`${window.meetingClientSettings.public.app.cdn + window.meetingClientSettings.public.app.basename + window.meetingClientSettings.public.app.instanceId}/resources/sounds/audioSample.mp3`);
|
||||
sound.addEventListener('ended', () => { sound.src = null; });
|
||||
if (deviceId && sound.setSinkId) sound.setSinkId(deviceId);
|
||||
sound.play();
|
||||
|
@ -0,0 +1,104 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import UserContainer from './user/container';
|
||||
|
||||
const CAPTIONS_CONFIG = window.meetingClientSettings.public.captions;
|
||||
|
||||
class LiveCaptions extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = { clear: true };
|
||||
this.timer = null;
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const { clear } = this.state;
|
||||
|
||||
if (clear) {
|
||||
const { transcript } = this.props;
|
||||
if (prevProps.transcript !== transcript) {
|
||||
// eslint-disable-next-line react/no-did-update-set-state
|
||||
this.setState({ clear: false });
|
||||
}
|
||||
} else {
|
||||
this.resetTimer();
|
||||
this.timer = setTimeout(() => this.setState({ clear: true }), CAPTIONS_CONFIG.time);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.resetTimer();
|
||||
}
|
||||
|
||||
resetTimer() {
|
||||
if (this.timer) {
|
||||
clearTimeout(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
transcript,
|
||||
transcriptId,
|
||||
} = this.props;
|
||||
|
||||
const { clear } = this.state;
|
||||
|
||||
const hasContent = transcript.length > 0 && !clear;
|
||||
|
||||
const wrapperStyles = {
|
||||
display: 'flex',
|
||||
};
|
||||
|
||||
const captionStyles = {
|
||||
whiteSpace: 'pre-line',
|
||||
wordWrap: 'break-word',
|
||||
fontFamily: 'Verdana, Arial, Helvetica, sans-serif',
|
||||
fontSize: '1.5rem',
|
||||
background: '#000000a0',
|
||||
color: 'white',
|
||||
padding: hasContent ? '.5rem' : undefined,
|
||||
};
|
||||
|
||||
const visuallyHidden = {
|
||||
position: 'absolute',
|
||||
overflow: 'hidden',
|
||||
clip: 'rect(0 0 0 0)',
|
||||
height: '1px',
|
||||
width: '1px',
|
||||
margin: '-1px',
|
||||
padding: '0',
|
||||
border: '0',
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={wrapperStyles}>
|
||||
{clear ? null : (
|
||||
<UserContainer
|
||||
background="#000000a0"
|
||||
transcriptId={transcriptId}
|
||||
/>
|
||||
)}
|
||||
<div style={captionStyles}>
|
||||
{clear ? '' : transcript}
|
||||
</div>
|
||||
<div
|
||||
style={visuallyHidden}
|
||||
aria-atomic
|
||||
aria-live="polite"
|
||||
>
|
||||
{clear ? '' : transcript}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
LiveCaptions.propTypes = {
|
||||
transcript: PropTypes.string.isRequired,
|
||||
transcriptId: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default LiveCaptions;
|
@ -0,0 +1,44 @@
|
||||
import React from 'react';
|
||||
import { withTracker } from 'meteor/react-meteor-data';
|
||||
import Users from '/imports/api/users';
|
||||
import User from './component';
|
||||
|
||||
const MODERATOR = window.meetingClientSettings.public.user.role_moderator;
|
||||
|
||||
const Container = (props) => <User {...props} />;
|
||||
|
||||
const getUser = (userId) => {
|
||||
const user = Users.findOne(
|
||||
{ userId },
|
||||
{
|
||||
fields: {
|
||||
avatar: 1,
|
||||
color: 1,
|
||||
role: 1,
|
||||
name: 1,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
if (user) {
|
||||
return {
|
||||
avatar: user.avatar,
|
||||
color: user.color,
|
||||
moderator: user.role === MODERATOR,
|
||||
name: user.name,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
avatar: '',
|
||||
color: '',
|
||||
moderator: false,
|
||||
name: '',
|
||||
};
|
||||
};
|
||||
|
||||
export default withTracker(({ transcriptId }) => {
|
||||
const userId = transcriptId.split('-')[0];
|
||||
|
||||
return getUser(userId);
|
||||
})(Container);
|
@ -7,7 +7,7 @@ import deviceInfo from '/imports/utils/deviceInfo';
|
||||
import { isLiveTranscriptionEnabled } from '/imports/ui/services/features';
|
||||
import { unique } from 'radash';
|
||||
|
||||
const CONFIG = Meteor.settings.public.app.audioCaptions;
|
||||
const CONFIG = window.meetingClientSettings.public.app.audioCaptions;
|
||||
const ENABLED = CONFIG.enabled;
|
||||
const PROVIDER = CONFIG.provider;
|
||||
const LANGUAGES = CONFIG.language.available;
|
||||
|
@ -25,8 +25,8 @@ import Settings from '/imports/ui/services/settings';
|
||||
import useToggleVoice from './audio-graphql/hooks/useToggleVoice';
|
||||
import { usePreviousValue } from '/imports/ui/components/utils/hooks';
|
||||
|
||||
const APP_CONFIG = Meteor.settings.public.app;
|
||||
const KURENTO_CONFIG = Meteor.settings.public.kurento;
|
||||
const APP_CONFIG = window.meetingClientSettings.public.app;
|
||||
const KURENTO_CONFIG = window.meetingClientSettings.public.kurento;
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
joinedAudio: {
|
||||
|
@ -1,13 +1,13 @@
|
||||
import LocalPCLoopback from '/imports/ui/services/webrtc-base/local-pc-loopback';
|
||||
import browserInfo from '/imports/utils/browserInfo';
|
||||
|
||||
const MEDIA_TAG = Meteor.settings.public.media.mediaTag;
|
||||
const USE_RTC_LOOPBACK_CHR = Meteor.settings.public.media.localEchoTest.useRtcLoopbackInChromium;
|
||||
const MEDIA_TAG = window.meetingClientSettings.public.media.mediaTag;
|
||||
const USE_RTC_LOOPBACK_CHR = window.meetingClientSettings.public.media.localEchoTest.useRtcLoopbackInChromium;
|
||||
const {
|
||||
enabled: DELAY_ENABLED = true,
|
||||
delayTime = 0.5,
|
||||
maxDelayTime = 2,
|
||||
} = Meteor.settings.public.media.localEchoTest.delay;
|
||||
} = window.meetingClientSettings.public.media.localEchoTest.delay;
|
||||
|
||||
let audioContext = null;
|
||||
let sourceContext = null;
|
||||
|
@ -8,13 +8,13 @@ import VoiceUsers from '/imports/api/voice-users';
|
||||
import logger from '/imports/startup/client/logger';
|
||||
import Storage from '../../services/storage/session';
|
||||
|
||||
const ROLE_MODERATOR = Meteor.settings.public.user.role_moderator;
|
||||
const TOGGLE_MUTE_THROTTLE_TIME = Meteor.settings.public.media.toggleMuteThrottleTime;
|
||||
const SHOW_VOLUME_METER = Meteor.settings.public.media.showVolumeMeter;
|
||||
const ROLE_MODERATOR = window.meetingClientSettings.public.user.role_moderator;
|
||||
const TOGGLE_MUTE_THROTTLE_TIME = window.meetingClientSettings.public.media.toggleMuteThrottleTime;
|
||||
const SHOW_VOLUME_METER = window.meetingClientSettings.public.media.showVolumeMeter;
|
||||
const {
|
||||
enabled: LOCAL_ECHO_TEST_ENABLED,
|
||||
initialHearingState: LOCAL_ECHO_INIT_HEARING_STATE,
|
||||
} = Meteor.settings.public.media.localEchoTest;
|
||||
} = window.meetingClientSettings.public.media.localEchoTest;
|
||||
|
||||
const MUTED_KEY = 'muted';
|
||||
|
||||
|
@ -38,12 +38,10 @@ class AuthenticatedHandler extends Component {
|
||||
if (Auth.loggedIn) {
|
||||
callback();
|
||||
}
|
||||
|
||||
AuthenticatedHandler.addReconnectObservable();
|
||||
|
||||
const setReason = (reason) => {
|
||||
const log = reason.error === 403 ? 'warn' : 'error';
|
||||
|
||||
logger[log]({
|
||||
logCode: 'authenticatedhandlercomponent_setreason',
|
||||
extraInfo: { reason },
|
||||
@ -70,6 +68,7 @@ class AuthenticatedHandler extends Component {
|
||||
|
||||
componentDidMount() {
|
||||
if (Session.get('codeError')) {
|
||||
console.log('Session.get(codeError)', Session.get('codeError'));
|
||||
this.setState({ authenticated: true });
|
||||
}
|
||||
AuthenticatedHandler.authenticatedRouteHandler((value, error) => {
|
||||
|
@ -0,0 +1,44 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import useAuthData from '/imports/ui/core/local-states/useAuthData';
|
||||
import Auth from '/imports/ui/services/auth';
|
||||
import { Session } from 'meteor/session';
|
||||
|
||||
interface PresenceAdapterProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const PresenceAdapter: React.FC<PresenceAdapterProps> = ({ children }) => {
|
||||
const [authData] = useAuthData();
|
||||
const [authSetted, setAuthSetted] = React.useState(false);
|
||||
useEffect(() => {
|
||||
const {
|
||||
authToken,
|
||||
logoutUrl,
|
||||
meetingId,
|
||||
sessionToken,
|
||||
userId,
|
||||
userName,
|
||||
extId,
|
||||
meetingName,
|
||||
} = authData;
|
||||
Auth.clearCredentials();
|
||||
Auth.set(
|
||||
meetingId,
|
||||
userId,
|
||||
authToken,
|
||||
logoutUrl,
|
||||
sessionToken,
|
||||
userName,
|
||||
extId,
|
||||
meetingName,
|
||||
);
|
||||
Auth.loggedIn = true;
|
||||
Auth.connectionAuthTime = new Date().getTime();
|
||||
Session.set('userWillAuth', false);
|
||||
setAuthSetted(true);
|
||||
}, []);
|
||||
|
||||
return authSetted ? children : null;
|
||||
};
|
||||
|
||||
export default PresenceAdapter;
|
@ -2,7 +2,6 @@ import React, { useMemo } from 'react';
|
||||
import ModalFullscreen from '/imports/ui/components/common/modal/fullscreen/component';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import { range } from 'ramda';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { uniqueId } from '/imports/utils/string-utils';
|
||||
import { isImportPresentationWithAnnotationsFromBreakoutRoomsEnabled, isImportSharedNotesFromBreakoutRoomsEnabled } from '/imports/ui/services/features';
|
||||
import useMeeting from '/imports/ui/core/hooks/useMeeting';
|
||||
@ -27,7 +26,7 @@ import {
|
||||
} from './room-managment-state/types';
|
||||
import { BREAKOUT_ROOM_CREATE, BREAKOUT_ROOM_MOVE_USER } from '../../mutations';
|
||||
|
||||
const BREAKOUT_LIM = Meteor.settings.public.app.breakouts.breakoutRoomLimit;
|
||||
const BREAKOUT_LIM = window.meetingClientSettings.public.app.breakouts.breakoutRoomLimit;
|
||||
const MIN_BREAKOUT_ROOMS = 2;
|
||||
const MAX_BREAKOUT_ROOMS = BREAKOUT_LIM > MIN_BREAKOUT_ROOMS ? BREAKOUT_LIM : MIN_BREAKOUT_ROOMS;
|
||||
const MIN_BREAKOUT_TIME = 5;
|
||||
|
@ -4,7 +4,7 @@ import MessageForm from './component';
|
||||
import ChatService from '/imports/ui/components/chat/service';
|
||||
import { BREAKOUT_ROOM_SEND_MESSAGE_TO_ALL } from '../mutations';
|
||||
|
||||
const CHAT_CONFIG = Meteor.settings.public.chat;
|
||||
const CHAT_CONFIG = window.meetingClientSettings.public.chat;
|
||||
const MessageFormContainer = (props) => {
|
||||
const [sendMessageToAllBreakouts] = useMutation(BREAKOUT_ROOM_SEND_MESSAGE_TO_ALL);
|
||||
|
||||
|
@ -2,7 +2,7 @@ import React, { PureComponent } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Service from '/imports/ui/components/captions/service';
|
||||
|
||||
const CAPTIONS_CONFIG = Meteor.settings.public.captions;
|
||||
const CAPTIONS_CONFIG = window.meetingClientSettings.public.captions;
|
||||
|
||||
class LiveCaptions extends PureComponent {
|
||||
constructor(props) {
|
||||
|
@ -7,7 +7,7 @@ import { Meteor } from 'meteor/meteor';
|
||||
import { Session } from 'meteor/session';
|
||||
import { isCaptionsEnabled } from '/imports/ui/services/features';
|
||||
|
||||
const CAPTIONS_CONFIG = Meteor.settings.public.captions;
|
||||
const CAPTIONS_CONFIG = window.meetingClientSettings.public.captions;
|
||||
const LINE_BREAK = '\n';
|
||||
|
||||
const getAvailableLocales = () => {
|
||||
|
@ -17,10 +17,11 @@ import { generateExportedMessages } from './services';
|
||||
import { getDateString } from '/imports/utils/string-utils';
|
||||
import { ChatCommands } from '/imports/ui/core/enums/chat';
|
||||
import { CHAT_PUBLIC_CLEAR_HISTORY } from './mutations';
|
||||
import useMeetingSettings from '/imports/ui/core/local-states/useMeetingSettings';
|
||||
|
||||
// @ts-ignore - temporary, while meteor exists in the project
|
||||
const CHAT_CONFIG = Meteor.settings.public.chat;
|
||||
const ENABLE_SAVE_AND_COPY_PUBLIC_CHAT = CHAT_CONFIG.enableSaveAndCopyPublicChat;
|
||||
// const CHAT_CONFIG = window.meetingClientSettings.public.chat;
|
||||
// const ENABLE_SAVE_AND_COPY_PUBLIC_CHAT = CHAT_CONFIG.enableSaveAndCopyPublicChat;
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
clear: {
|
||||
@ -54,6 +55,9 @@ const intlMessages = defineMessages({
|
||||
});
|
||||
|
||||
const ChatActions: React.FC = () => {
|
||||
const [MeetingSettings] = useMeetingSettings();
|
||||
const chatConfig = MeetingSettings.public.chat;
|
||||
const { enableSaveAndCopyPublicChat } = chatConfig;
|
||||
const intl = useIntl();
|
||||
const isRTL = layoutSelect((i: Layout) => i.isRTL);
|
||||
const uniqueIdsRef = useRef<string[]>([uid(1), uid(2), uid(3), uid(4)]);
|
||||
@ -116,7 +120,7 @@ const ChatActions: React.FC = () => {
|
||||
const dropdownActions = [
|
||||
{
|
||||
key: uniqueIdsRef.current[0],
|
||||
enable: ENABLE_SAVE_AND_COPY_PUBLIC_CHAT,
|
||||
enable: enableSaveAndCopyPublicChat,
|
||||
icon: 'download',
|
||||
dataTest: 'chatSave',
|
||||
label: intl.formatMessage(intlMessages.save),
|
||||
@ -127,7 +131,7 @@ const ChatActions: React.FC = () => {
|
||||
},
|
||||
{
|
||||
key: uniqueIdsRef.current[1],
|
||||
enable: ENABLE_SAVE_AND_COPY_PUBLIC_CHAT,
|
||||
enable: enableSaveAndCopyPublicChat,
|
||||
icon: 'copy',
|
||||
id: 'clipboardButton',
|
||||
dataTest: 'chatCopy',
|
||||
|
@ -38,7 +38,7 @@ import { GraphqlDataHookSubscriptionResponse } from '/imports/ui/Types/hook';
|
||||
import { throttle } from '/imports/utils/throttle';
|
||||
|
||||
// @ts-ignore - temporary, while meteor exists in the project
|
||||
const CHAT_CONFIG = Meteor.settings.public.chat;
|
||||
const CHAT_CONFIG = window.meetingClientSettings.public.chat;
|
||||
|
||||
const PUBLIC_CHAT_ID = CHAT_CONFIG.public_id;
|
||||
const PUBLIC_GROUP_CHAT_ID = CHAT_CONFIG.public_group_id;
|
||||
@ -114,9 +114,9 @@ const messages = defineMessages({
|
||||
});
|
||||
|
||||
// @ts-ignore - temporary, while meteor exists in the project
|
||||
const AUTO_CONVERT_EMOJI = Meteor.settings.public.chat.autoConvertEmoji;
|
||||
const AUTO_CONVERT_EMOJI = window.meetingClientSettings.public.chat.autoConvertEmoji;
|
||||
// @ts-ignore - temporary, while meteor exists in the project
|
||||
const ENABLE_EMOJI_PICKER = Meteor.settings.public.chat.emojiPicker.enable;
|
||||
const ENABLE_EMOJI_PICKER = window.meetingClientSettings.public.chat.emojiPicker.enable;
|
||||
const ENABLE_TYPING_INDICATOR = CHAT_CONFIG.typingIndicator.enabled;
|
||||
|
||||
const ChatMessageForm: React.FC<ChatMessageFormProps> = ({
|
||||
|
@ -5,7 +5,6 @@ import React, {
|
||||
useState,
|
||||
useMemo,
|
||||
} from 'react';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { makeVar, useMutation } from '@apollo/client';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import LAST_SEEN_MUTATION from './queries';
|
||||
@ -27,7 +26,7 @@ import { Layout } from '../../../layout/layoutTypes';
|
||||
import { GraphqlDataHookSubscriptionResponse } from '/imports/ui/Types/hook';
|
||||
|
||||
// @ts-ignore - temporary, while meteor exists in the project
|
||||
const CHAT_CONFIG = Meteor.settings.public.chat;
|
||||
const CHAT_CONFIG = window.meetingClientSettings.public.chat;
|
||||
const PUBLIC_CHAT_KEY = CHAT_CONFIG.public_id;
|
||||
const PUBLIC_GROUP_CHAT_KEY = CHAT_CONFIG.public_group_id;
|
||||
|
||||
|
@ -4,7 +4,7 @@ import Styled from './styles';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore - temporary, while meteor exists in the project
|
||||
const APP_CONFIG = Meteor.settings.public.app;
|
||||
const APP_CONFIG = window.meetingClientSettings.public.app;
|
||||
|
||||
interface ChatMessagePresentationContentProps {
|
||||
metadata: string;
|
||||
|
@ -14,7 +14,7 @@ const ChatMessageTextContent: React.FC<ChatMessageTextContentProps> = ({
|
||||
}) => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore - temporary, while meteor exists in the project
|
||||
const { allowedElements } = Meteor.settings.public.chat;
|
||||
const { allowedElements } = window.meetingClientSettings.public.chat;
|
||||
|
||||
return (
|
||||
<Styled.ChatMessage systemMsg={systemMsg} emphasizedMessage={emphasizedMessage} data-test="messageContent">
|
||||
|
@ -16,7 +16,7 @@ import { useCreateUseSubscription } from '/imports/ui/core/hooks/createUseSubscr
|
||||
import { setLoadedMessageGathering } from '/imports/ui/core/hooks/useLoadedChatMessages';
|
||||
|
||||
// @ts-ignore - temporary, while meteor exists in the project
|
||||
const CHAT_CONFIG = Meteor.settings.public.chat;
|
||||
const CHAT_CONFIG = window.meetingClientSettings.public.chat;
|
||||
const PUBLIC_GROUP_CHAT_KEY = CHAT_CONFIG.public_group_id;
|
||||
|
||||
interface ChatListPageContainerProps {
|
||||
|
@ -16,7 +16,7 @@ interface ChatPopupProps {
|
||||
const WELCOME_MSG_KEY = 'welcomeMsg';
|
||||
const WELCOME_MSG_FOR_MODERATORS_KEY = 'welcomeMsgForModerators';
|
||||
// @ts-ignore - temporary, while meteor exists in the project
|
||||
const CHAT_CONFIG = Meteor.settings.public.chat;
|
||||
const CHAT_CONFIG = window.meetingClientSettings.public.chat;
|
||||
const PUBLIC_GROUP_CHAT_KEY = CHAT_CONFIG.public_group_id;
|
||||
|
||||
const setWelcomeMsgsOnSession = (key: string, value: boolean) => {
|
||||
|
@ -24,7 +24,7 @@ const DEBUG_CONSOLE = false;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore - temporary, while meteor exists in the project
|
||||
const CHAT_CONFIG = Meteor.settings.public.chat;
|
||||
const CHAT_CONFIG = window.meetingClientSettings.public.chat;
|
||||
const PUBLIC_GROUP_CHAT_KEY = CHAT_CONFIG.public_group_id;
|
||||
const TYPING_INDICATOR_ENABLED = CHAT_CONFIG.typingIndicator.enabled;
|
||||
|
||||
|
@ -8,7 +8,8 @@ import { meetingIsBreakout } from '/imports/ui/components/app/service';
|
||||
import { defineMessages } from 'react-intl';
|
||||
import PollService from '/imports/ui/components/poll/service';
|
||||
|
||||
const CHAT_CONFIG = Meteor.settings.public.chat;
|
||||
const APP = window.meetingClientSettings.public.app;
|
||||
const CHAT_CONFIG = window.meetingClientSettings.public.chat;
|
||||
const GROUPING_MESSAGES_WINDOW = CHAT_CONFIG.grouping_messages_window;
|
||||
|
||||
const SYSTEM_CHAT_TYPE = CHAT_CONFIG.type_system;
|
||||
@ -19,7 +20,7 @@ const PUBLIC_GROUP_CHAT_ID = CHAT_CONFIG.public_group_id;
|
||||
const PUBLIC_CHAT_CLEAR = CHAT_CONFIG.system_messages_keys.chat_clear;
|
||||
const CHAT_POLL_RESULTS_MESSAGE = CHAT_CONFIG.system_messages_keys.chat_poll_result;
|
||||
|
||||
const ROLE_MODERATOR = Meteor.settings.public.user.role_moderator;
|
||||
const ROLE_MODERATOR = window.meetingClientSettings.public.user.role_moderator;
|
||||
|
||||
const ScrollCollection = new Mongo.Collection(null);
|
||||
|
||||
|
@ -1,16 +1,43 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import logger from '/imports/startup/client/logger';
|
||||
import logger, { generateLoggerStreams } from '/imports/startup/client/logger';
|
||||
|
||||
const propTypes = {
|
||||
children: PropTypes.element.isRequired,
|
||||
Fallback: PropTypes.func.isRequired,
|
||||
Fallback: PropTypes.element,
|
||||
errorMessage: PropTypes.string,
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
Fallback: null,
|
||||
errorMessage: 'Something went wrong',
|
||||
};
|
||||
|
||||
class ErrorBoundary extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { error: false, errorInfo: null };
|
||||
this.state = { error: '', errorInfo: null };
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const data = JSON.parse((sessionStorage.getItem('clientStartupSettings')) || {});
|
||||
const logConfig = data?.clientLog;
|
||||
if (logConfig) {
|
||||
generateLoggerStreams(logConfig).forEach((stream) => {
|
||||
logger.addStream(stream);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
const { code, error, errorInfo } = this.state;
|
||||
const log = code === '403' ? 'warn' : 'error';
|
||||
if (error || errorInfo) {
|
||||
logger[log]({
|
||||
logCode: 'Error_Boundary_wrapper',
|
||||
extraInfo: { error, errorInfo },
|
||||
}, 'generic error boundary logger');
|
||||
}
|
||||
}
|
||||
|
||||
componentDidCatch(error, errorInfo) {
|
||||
@ -18,26 +45,27 @@ class ErrorBoundary extends Component {
|
||||
error,
|
||||
errorInfo,
|
||||
});
|
||||
logger.error({
|
||||
logCode: 'Error_Boundary_wrapper',
|
||||
extraInfo: { error, errorInfo },
|
||||
}, 'generic error boundary logger');
|
||||
}
|
||||
|
||||
render() {
|
||||
const { error } = this.state;
|
||||
const { children, Fallback } = this.props;
|
||||
const { error, errorInfo } = this.state;
|
||||
const { children, Fallback, errorMessage } = this.props;
|
||||
|
||||
return (error ? (<Fallback {...this.state} />) : children);
|
||||
const fallbackElement = Fallback && error
|
||||
? <Fallback error={error || {}} errorInfo={errorInfo} /> : <div>{errorMessage}</div>;
|
||||
return (error
|
||||
? fallbackElement
|
||||
: children);
|
||||
}
|
||||
}
|
||||
|
||||
ErrorBoundary.propTypes = propTypes;
|
||||
ErrorBoundary.defaultProps = defaultProps;
|
||||
|
||||
export default ErrorBoundary;
|
||||
|
||||
export const withErrorBoundary = (WrappedComponent, FallbackComponent) => (props) => (
|
||||
<ErrorBoundary Fallback={FallbackComponent}>
|
||||
<WrappedComponent {...props} />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
|
||||
export default ErrorBoundary;
|
||||
|
@ -0,0 +1,24 @@
|
||||
import React from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import ErrorBoundary from '../component';
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
errorMessage: {
|
||||
id: 'app.presentationUploder.genericError',
|
||||
defaultMessage: 'Something went wrong',
|
||||
},
|
||||
});
|
||||
|
||||
const LocatedErrorBoundary = ({ children, ...props }) => {
|
||||
const intl = useIntl();
|
||||
return (
|
||||
<ErrorBoundary
|
||||
{...props}
|
||||
errorMessage={intl.formatMessage(intlMessages.errorMessage)}
|
||||
>
|
||||
{children}
|
||||
</ErrorBoundary>
|
||||
);
|
||||
};
|
||||
|
||||
export default LocatedErrorBoundary;
|
@ -1,14 +1,11 @@
|
||||
import React from 'react';
|
||||
import Styled from './styles';
|
||||
import Settings from '/imports/ui/services/settings';
|
||||
|
||||
const { animations } = Settings.application;
|
||||
|
||||
const LoadingScreen = ({ children }) => (
|
||||
<Styled.Background>
|
||||
<Styled.Spinner animations={animations}>
|
||||
<Styled.Bounce1 animations={animations} />
|
||||
<Styled.Bounce2 animations={animations} />
|
||||
<Styled.Spinner animations>
|
||||
<Styled.Bounce1 animations />
|
||||
<Styled.Bounce2 animations />
|
||||
<div />
|
||||
</Styled.Spinner>
|
||||
<Styled.Message>
|
||||
|
@ -0,0 +1,57 @@
|
||||
import React from 'react';
|
||||
import LoadingScreen from '../component';
|
||||
|
||||
interface LoadingContent {
|
||||
isLoading: boolean;
|
||||
loadingMessage: string;
|
||||
}
|
||||
|
||||
interface LoadingContextContent extends LoadingContent {
|
||||
setLoading: (isLoading: boolean, loadingMessage: string) => void;
|
||||
}
|
||||
|
||||
export const LoadingContext = React.createContext<LoadingContextContent>({
|
||||
isLoading: false,
|
||||
loadingMessage: '',
|
||||
setLoading: () => { },
|
||||
});
|
||||
|
||||
interface LoadingScreenHOCProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const LoadingScreenHOC: React.FC<LoadingScreenHOCProps> = ({
|
||||
children,
|
||||
}) => {
|
||||
const [loading, setLoading] = React.useState<LoadingContent>({
|
||||
isLoading: false,
|
||||
loadingMessage: '',
|
||||
});
|
||||
|
||||
return (
|
||||
<LoadingContext.Provider value={{
|
||||
loadingMessage: loading.loadingMessage,
|
||||
isLoading: loading.isLoading,
|
||||
setLoading: (isLoading: boolean, loadingMessage: string = '') => {
|
||||
setLoading({
|
||||
isLoading,
|
||||
loadingMessage,
|
||||
});
|
||||
},
|
||||
}}
|
||||
>
|
||||
{
|
||||
loading.isLoading
|
||||
? (
|
||||
<LoadingScreen>
|
||||
<h1>{loading.loadingMessage}</h1>
|
||||
</LoadingScreen>
|
||||
)
|
||||
: null
|
||||
}
|
||||
{children}
|
||||
</LoadingContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoadingScreenHOC;
|
@ -22,7 +22,7 @@ class LocalesDropdown extends PureComponent {
|
||||
filterLocaleVariations(value) {
|
||||
const { allLocales } = this.props;
|
||||
if (allLocales) {
|
||||
if (Meteor.settings.public.app.showAllAvailableLocales) {
|
||||
if (window.meetingClientSettings.public.app.showAllAvailableLocales) {
|
||||
return allLocales;
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,7 @@ import ModalSimple from '/imports/ui/components/common/modal/simple/component';
|
||||
import AudioService from '/imports/ui/components/audio/service';
|
||||
import Styled from './styles';
|
||||
|
||||
const SELECT_RANDOM_USER_COUNTDOWN = Meteor.settings.public.selectRandomUser.countdown;
|
||||
const SELECT_RANDOM_USER_COUNTDOWN = window.meetingClientSettings.public.selectRandomUser.countdown;
|
||||
|
||||
const messages = defineMessages({
|
||||
noViewers: {
|
||||
@ -102,9 +102,9 @@ class RandomUserSelect extends Component {
|
||||
}
|
||||
|
||||
play() {
|
||||
AudioService.playAlertSound(`${Meteor.settings.public.app.cdn
|
||||
+ Meteor.settings.public.app.basename
|
||||
+ Meteor.settings.public.app.instanceId}`
|
||||
AudioService.playAlertSound(`${window.meetingClientSettings.public.app.cdn
|
||||
+ window.meetingClientSettings.public.app.basename
|
||||
+ window.meetingClientSettings.public.app.instanceId}`
|
||||
+ '/resources/sounds/Poll.mp3');
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,7 @@ import useCurrentUser from '/imports/ui/core/hooks/useCurrentUser';
|
||||
import { useMutation } from '@apollo/client';
|
||||
import { PICK_RANDOM_VIEWER } from '/imports/ui/core/graphql/mutations/userMutations';
|
||||
|
||||
const SELECT_RANDOM_USER_ENABLED = Meteor.settings.public.selectRandomUser.enabled;
|
||||
const SELECT_RANDOM_USER_ENABLED = window.meetingClientSettings.public.selectRandomUser.enabled;
|
||||
|
||||
// A value that is used by component to remember
|
||||
// whether it should be open or closed after a render
|
||||
|
@ -0,0 +1,107 @@
|
||||
import {
|
||||
ApolloClient, ApolloProvider, InMemoryCache, NormalizedCacheObject,
|
||||
} from '@apollo/client';
|
||||
import { WebSocketLink } from '@apollo/client/link/ws';
|
||||
import { SubscriptionClient } from 'subscriptions-transport-ws';
|
||||
import React, { useContext, useEffect } from 'react';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { LoadingContext } from '/imports/ui/components/common/loading-screen/loading-screen-HOC/component';
|
||||
|
||||
interface ConnectionManagerProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
interface Response {
|
||||
response: {
|
||||
returncode: string;
|
||||
version: string;
|
||||
apiVersion: string;
|
||||
bbbVersion: string;
|
||||
graphqlWebsocketUrl: string;
|
||||
}
|
||||
}
|
||||
|
||||
const ConnectionManager: React.FC<ConnectionManagerProps> = ({ children }): React.ReactNode => {
|
||||
const [graphqlUrlApolloClient, setApolloClient] = React.useState<ApolloClient<NormalizedCacheObject> | null>(null);
|
||||
const [graphqlUrl, setGraphqlUrl] = React.useState<string>('');
|
||||
const loadingContextInfo = useContext(LoadingContext);
|
||||
useEffect(() => {
|
||||
fetch(`https://${window.location.hostname}/bigbluebutton/api`, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}).then(async (response) => {
|
||||
const responseJson: Response = await response.json();
|
||||
setGraphqlUrl(responseJson.response.graphqlWebsocketUrl);
|
||||
}).catch((error) => {
|
||||
loadingContextInfo.setLoading(false, '');
|
||||
throw new Error('Error fetching GraphQL URL: '.concat(error.message || ''));
|
||||
});
|
||||
loadingContextInfo.setLoading(true, 'Fetching GraphQL URL');
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
loadingContextInfo.setLoading(true, 'Connecting to GraphQL server');
|
||||
if (graphqlUrl) {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const sessionToken = urlParams.get('sessionToken');
|
||||
if (!sessionToken) {
|
||||
loadingContextInfo.setLoading(false, '');
|
||||
throw new Error('Missing session token');
|
||||
}
|
||||
sessionStorage.setItem('sessionToken', sessionToken);
|
||||
|
||||
let wsLink;
|
||||
try {
|
||||
const subscription = new SubscriptionClient(graphqlUrl, {
|
||||
reconnect: true,
|
||||
timeout: 30000,
|
||||
connectionParams: {
|
||||
headers: {
|
||||
'X-Session-Token': sessionToken,
|
||||
},
|
||||
},
|
||||
});
|
||||
subscription.onError(() => {
|
||||
loadingContextInfo.setLoading(false, '');
|
||||
throw new Error('Error: on subscription to server');
|
||||
});
|
||||
wsLink = new WebSocketLink(
|
||||
subscription,
|
||||
);
|
||||
wsLink.setOnError((error) => {
|
||||
loadingContextInfo.setLoading(false, '');
|
||||
throw new Error('Error: on apollo connection'.concat(JSON.stringify(error) || ''));
|
||||
});
|
||||
} catch (error) {
|
||||
loadingContextInfo.setLoading(false, '');
|
||||
throw new Error('Error creating WebSocketLink: '.concat(JSON.stringify(error) || ''));
|
||||
}
|
||||
let client;
|
||||
try {
|
||||
client = new ApolloClient({
|
||||
link: wsLink,
|
||||
cache: new InMemoryCache(),
|
||||
connectToDevTools: Meteor.isDevelopment,
|
||||
});
|
||||
setApolloClient(client);
|
||||
} catch (error) {
|
||||
loadingContextInfo.setLoading(false, '');
|
||||
throw new Error('Error creating Apollo Client: '.concat(JSON.stringify(error) || ''));
|
||||
}
|
||||
}
|
||||
},
|
||||
[graphqlUrl]);
|
||||
return (
|
||||
graphqlUrlApolloClient
|
||||
? (
|
||||
<ApolloProvider
|
||||
client={graphqlUrlApolloClient}
|
||||
>
|
||||
{children}
|
||||
</ApolloProvider>
|
||||
) : null
|
||||
);
|
||||
};
|
||||
|
||||
export default ConnectionManager;
|
@ -0,0 +1,98 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { ErrorScreen } from '../../error-screen/component';
|
||||
import LoadingScreen from '../../common/loading-screen/component';
|
||||
|
||||
const connectionTimeout = 60000;
|
||||
|
||||
interface Response {
|
||||
meeting_clientSettings: Array<{
|
||||
askForFeedbackOnLogout: boolean,
|
||||
allowDefaultLogoutUrl: boolean,
|
||||
learningDashboardBase: string,
|
||||
fallbackLocale: string,
|
||||
fallbackOnEmptyString: boolean,
|
||||
clientLog: {
|
||||
server: {
|
||||
level: string,
|
||||
enabled: boolean
|
||||
},
|
||||
console: {
|
||||
level: string,
|
||||
enabled: true
|
||||
},
|
||||
external: {
|
||||
url: string,
|
||||
level: string,
|
||||
logTag: string,
|
||||
method: string,
|
||||
enabled: boolean,
|
||||
flushOnClose: boolean,
|
||||
throttleInterval: number,
|
||||
}
|
||||
}
|
||||
}>
|
||||
}
|
||||
|
||||
interface StartupDataFetchProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const StartupDataFetch: React.FC<StartupDataFetchProps> = ({
|
||||
children,
|
||||
}) => {
|
||||
const [settingsFetched, setSettingsFetched] = React.useState<boolean>(false);
|
||||
const [error, setError] = React.useState<string>('');
|
||||
const timeoutRef = React.useRef<ReturnType<typeof setTimeout>>();
|
||||
const [loading, setLoading] = React.useState<boolean>(false);
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
timeoutRef.current = setTimeout(() => {
|
||||
setError('Timeout on fetching startup data');
|
||||
setLoading(false);
|
||||
}, connectionTimeout);
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const sessionToken = urlParams.get('sessionToken');
|
||||
|
||||
if (!sessionToken) {
|
||||
setError('Missing session token');
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
const clientStartupSettings = '/api/rest/clientStartupSettings/';
|
||||
const url = new URL(`${window.location.origin}${clientStartupSettings}`);
|
||||
const headers = new Headers({ 'X-Session-Token': sessionToken, 'Content-Type': 'application/json' });
|
||||
fetch(url, { method: 'get', headers })
|
||||
.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);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
{settingsFetched ? children : null}
|
||||
{error
|
||||
? (
|
||||
<ErrorScreen
|
||||
endedReason={error}
|
||||
/>
|
||||
)
|
||||
: null}
|
||||
{loading
|
||||
? (
|
||||
<LoadingScreen>
|
||||
<div style={{ display: 'none' }}>
|
||||
Loading...
|
||||
</div>
|
||||
</LoadingScreen>
|
||||
)
|
||||
: null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default StartupDataFetch;
|
@ -3,7 +3,7 @@ import { useMutation, useSubscription } from '@apollo/client';
|
||||
import { CONNECTION_STATUS_SUBSCRIPTION } from './queries';
|
||||
import { UPDATE_CONNECTION_ALIVE_AT, UPDATE_USER_CLIENT_RTT } from './mutations';
|
||||
|
||||
const STATS_INTERVAL = Meteor.settings.public.stats.interval;
|
||||
const STATS_INTERVAL = window.meetingClientSettings.public.stats.interval;
|
||||
|
||||
const ConnectionStatus = () => {
|
||||
const networkRttInMs = useRef(null); // Ref to store the current timeout
|
||||
|
@ -391,7 +391,7 @@ class ConnectionStatusComponent extends PureComponent {
|
||||
* @return {Object} The component to be renderized.
|
||||
*/
|
||||
renderNetworkData() {
|
||||
const { enableNetworkStats } = Meteor.settings.public.app;
|
||||
const { enableNetworkStats } = window.meetingClientSettings.public.app;
|
||||
|
||||
if (!enableNetworkStats) {
|
||||
return null;
|
||||
@ -497,7 +497,7 @@ class ConnectionStatusComponent extends PureComponent {
|
||||
* @return {Object} - The component to be renderized
|
||||
*/
|
||||
renderCopyDataButton() {
|
||||
const { enableCopyNetworkStatsButton } = Meteor.settings.public.app;
|
||||
const { enableCopyNetworkStatsButton } = window.meetingClientSettings.public.app;
|
||||
|
||||
if (!enableCopyNetworkStatsButton) {
|
||||
return null;
|
||||
|
@ -7,9 +7,9 @@ import AudioService from '/imports/ui/components/audio/service';
|
||||
import VideoService from '/imports/ui/components/video-provider/service';
|
||||
import ScreenshareService from '/imports/ui/components/screenshare/service';
|
||||
|
||||
const STATS = Meteor.settings.public.stats;
|
||||
const STATS = window.meetingClientSettings.public.stats;
|
||||
const NOTIFICATION = STATS.notification;
|
||||
const ROLE_MODERATOR = Meteor.settings.public.user.role_moderator;
|
||||
const ROLE_MODERATOR = window.meetingClientSettings.public.user.role_moderator;
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
saved: {
|
||||
|
@ -43,8 +43,8 @@ const intlMessages = defineMessages({
|
||||
},
|
||||
});
|
||||
|
||||
const DEBUG_WINDOW_ENABLED = Meteor.settings.public.app.enableDebugWindow;
|
||||
const SHOW_DEBUG_WINDOW_ACCESSKEY = Meteor.settings.public.app.shortcuts.openDebugWindow.accesskey;
|
||||
const DEBUG_WINDOW_ENABLED = window.meetingClientSettings.public.app.enableDebugWindow;
|
||||
const SHOW_DEBUG_WINDOW_ACCESSKEY = window.meetingClientSettings.public.app.shortcuts.openDebugWindow.accesskey;
|
||||
|
||||
class DebugWindow extends Component {
|
||||
constructor(props) {
|
||||
|
@ -4,7 +4,7 @@ import { injectIntl } from 'react-intl';
|
||||
import data from '@emoji-mart/data';
|
||||
import Picker from '@emoji-mart/react';
|
||||
|
||||
const DISABLE_EMOJIS = Meteor.settings.public.chat.disableEmojis;
|
||||
const DISABLE_EMOJIS = window.meetingClientSettings.public.chat.disableEmojis;
|
||||
|
||||
const propTypes = {
|
||||
intl: PropTypes.shape({
|
||||
|
@ -5,9 +5,9 @@ import Service from './service';
|
||||
const EmojiRain = ({ reactions }) => {
|
||||
const containerRef = useRef(null);
|
||||
const [isAnimating, setIsAnimating] = useState(false);
|
||||
const EMOJI_SIZE = Meteor.settings.public.app.emojiRain.emojiSize;
|
||||
const NUMBER_OF_EMOJIS = Meteor.settings.public.app.emojiRain.numberOfEmojis;
|
||||
const EMOJI_RAIN_ENABLED = Meteor.settings.public.app.emojiRain.enabled;
|
||||
const EMOJI_SIZE = window.meetingClientSettings.public.app.emojiRain.emojiSize;
|
||||
const NUMBER_OF_EMOJIS = window.meetingClientSettings.public.app.emojiRain.numberOfEmojis;
|
||||
const EMOJI_RAIN_ENABLED = window.meetingClientSettings.public.app.emojiRain.enabled;
|
||||
|
||||
const { animations } = Settings.application;
|
||||
|
||||
|
@ -26,7 +26,7 @@ const intlMessages = defineMessages({
|
||||
},
|
||||
});
|
||||
|
||||
const { warnAboutUnsavedContentOnMeetingEnd } = Meteor.settings.public.app;
|
||||
const { warnAboutUnsavedContentOnMeetingEnd } = window.meetingClientSettings.public.app;
|
||||
|
||||
const propTypes = {
|
||||
intl: PropTypes.shape({
|
||||
|
@ -3,8 +3,6 @@ import PropTypes from 'prop-types';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { Session } from 'meteor/session';
|
||||
import AudioManager from '/imports/ui/services/audio-manager';
|
||||
import logger from '/imports/startup/client/logger';
|
||||
import Styled from './styles';
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
@ -80,21 +78,29 @@ const propTypes = {
|
||||
PropTypes.string,
|
||||
PropTypes.number,
|
||||
]),
|
||||
error: PropTypes.object,
|
||||
errorInfo: PropTypes.object,
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
code: '500',
|
||||
callback: () => {},
|
||||
endedReason: null,
|
||||
error: {},
|
||||
errorInfo: null,
|
||||
};
|
||||
|
||||
class ErrorScreen extends PureComponent {
|
||||
componentDidMount() {
|
||||
const { code, callback, endedReason } = this.props;
|
||||
const log = code === '403' ? 'warn' : 'error';
|
||||
AudioManager.exitAudio();
|
||||
// stop audio
|
||||
document.querySelector('audio').pause();
|
||||
navigator
|
||||
.mediaDevices
|
||||
.getUserMedia({ audio: true, video: true })
|
||||
.then((m) => m.getTracks().forEach((t) => t.stop()));
|
||||
callback(endedReason, () => Meteor.disconnect());
|
||||
logger[log]({ logCode: 'startup_client_usercouldnotlogin_error' }, `User could not log in HTML5, hit ${code}`);
|
||||
console.error({ logCode: 'startup_client_usercouldnotlogin_error' }, `User could not log in HTML5, hit ${code}`);
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -102,18 +108,27 @@ class ErrorScreen extends PureComponent {
|
||||
intl,
|
||||
code,
|
||||
children,
|
||||
error,
|
||||
errorInfo,
|
||||
} = this.props;
|
||||
let formatedMessage = 'Oops, something went wrong';
|
||||
let errorMessageDescription = Session.get('errorMessageDescription');
|
||||
if (intl) {
|
||||
formatedMessage = intl.formatMessage(intlMessages[defaultProps.code]);
|
||||
|
||||
let formatedMessage = intl.formatMessage(intlMessages[defaultProps.code]);
|
||||
if (code in intlMessages) {
|
||||
formatedMessage = intl.formatMessage(intlMessages[code]);
|
||||
}
|
||||
|
||||
if (code in intlMessages) {
|
||||
formatedMessage = intl.formatMessage(intlMessages[code]);
|
||||
errorMessageDescription = Session.get('errorMessageDescription');
|
||||
|
||||
if (errorMessageDescription in intlMessages) {
|
||||
errorMessageDescription = intl.formatMessage(intlMessages[errorMessageDescription]);
|
||||
}
|
||||
}
|
||||
|
||||
let errorMessageDescription = Session.get('errorMessageDescription');
|
||||
|
||||
if (errorMessageDescription in intlMessages) {
|
||||
errorMessageDescription = intl.formatMessage(intlMessages[errorMessageDescription]);
|
||||
if (error) {
|
||||
errorMessageDescription = error.message;
|
||||
}
|
||||
|
||||
return (
|
||||
@ -130,6 +145,19 @@ class ErrorScreen extends PureComponent {
|
||||
</Styled.SessionMessage>
|
||||
)
|
||||
}
|
||||
{
|
||||
errorInfo
|
||||
? (
|
||||
<textarea
|
||||
rows="5"
|
||||
cols="33"
|
||||
disabled
|
||||
>
|
||||
{JSON.stringify(errorInfo)}
|
||||
</textarea>
|
||||
)
|
||||
: null
|
||||
}
|
||||
<Styled.Separator />
|
||||
<Styled.CodeError>
|
||||
{code}
|
||||
@ -144,5 +172,7 @@ class ErrorScreen extends PureComponent {
|
||||
|
||||
export default injectIntl(ErrorScreen);
|
||||
|
||||
export { ErrorScreen };
|
||||
|
||||
ErrorScreen.propTypes = propTypes;
|
||||
ErrorScreen.defaultProps = defaultProps;
|
||||
|
@ -1,56 +0,0 @@
|
||||
import {
|
||||
ApolloClient, ApolloProvider, InMemoryCache, NormalizedCacheObject,
|
||||
} from '@apollo/client';
|
||||
// import { WebSocketLink } from "@apollo/client/link/ws";
|
||||
import { WebSocketLink } from '@apollo/client/link/ws';
|
||||
import { SubscriptionClient } from 'subscriptions-transport-ws';
|
||||
import React, { useEffect } from 'react';
|
||||
import Auth from '/imports/ui/services/auth';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const GraphqlProvider = ({ children }: Props): React.ReactNode => {
|
||||
// const [link, setLink] = React.useState<WebSocketLink | null>(null);
|
||||
const [apolloClient, setApolloClient] = React.useState<ApolloClient<NormalizedCacheObject> | null>(null);
|
||||
useEffect(() => {
|
||||
let GRAPHQL_URL = null;
|
||||
if ('graphqlUrl' in Meteor.settings.public.app) {
|
||||
GRAPHQL_URL = Meteor.settings.public.app.graphqlUrl;
|
||||
} else {
|
||||
GRAPHQL_URL = `wss://${window.location.hostname}/v1/graphql`;
|
||||
}
|
||||
const wsLink = new WebSocketLink(
|
||||
new SubscriptionClient(GRAPHQL_URL, {
|
||||
reconnect: true,
|
||||
timeout: 30000,
|
||||
connectionParams: {
|
||||
headers: {
|
||||
'X-Session-Token': Auth.sessionToken,
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
// setLink(wsLink);
|
||||
const client = new ApolloClient({
|
||||
link: wsLink,
|
||||
cache: new InMemoryCache(),
|
||||
connectToDevTools: Meteor.isDevelopment,
|
||||
});
|
||||
setApolloClient(client);
|
||||
}, []);
|
||||
return (
|
||||
apolloClient
|
||||
&& (
|
||||
<ApolloProvider
|
||||
client={apolloClient}
|
||||
>
|
||||
{children}
|
||||
</ApolloProvider>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
export default GraphqlProvider;
|
@ -85,7 +85,7 @@ class JoinHandler extends Component {
|
||||
|
||||
async fetchToken() {
|
||||
const { hasAlreadyJoined } = this.state;
|
||||
const APP = Meteor.settings.public.app;
|
||||
const APP = window.meetingClientSettings.public.app;
|
||||
if (!this._isMounted) return;
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
@ -112,7 +112,7 @@ class JoinHandler extends Component {
|
||||
userAgent: userInfo.userAgent,
|
||||
screenSize: { width: window.screen.width, height: window.screen.height },
|
||||
windowSize: { width: window.innerWidth, height: window.innerHeight },
|
||||
bbbVersion: Meteor.settings.public.app.bbbServerVersion,
|
||||
bbbVersion: window.meetingClientSettings.public.app.bbbServerVersion,
|
||||
location: window.location.href,
|
||||
};
|
||||
|
||||
@ -210,14 +210,13 @@ class JoinHandler extends Component {
|
||||
},
|
||||
}, 'User successfully went through main.joinRouteHandler');
|
||||
} else {
|
||||
|
||||
if(['missingSession','meetingForciblyEnded','notFound'].includes(response.messageKey)) {
|
||||
if (['missingSession', 'meetingForciblyEnded', 'notFound'].includes(response.messageKey)) {
|
||||
JoinHandler.setError('410');
|
||||
Session.set('errorMessageDescription', 'meeting_ended');
|
||||
} else if(response.messageKey == "guestDeny") {
|
||||
} else if (response.messageKey == "guestDeny") {
|
||||
JoinHandler.setError('401');
|
||||
Session.set('errorMessageDescription', 'guest_deny');
|
||||
} else if(response.messageKey == "maxParticipantsReached") {
|
||||
} else if (response.messageKey == "maxParticipantsReached") {
|
||||
JoinHandler.setError('401');
|
||||
Session.set('errorMessageDescription', 'max_participants_reason');
|
||||
} else {
|
||||
|
@ -0,0 +1,55 @@
|
||||
import { useQuery } from '@apollo/client';
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { UserCustomParameterResponse, getCustomParameter } from './queries';
|
||||
|
||||
interface CustomUsersSettingsProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const CustomUsersSettings: React.FC<CustomUsersSettingsProps> = ({
|
||||
children,
|
||||
}) => {
|
||||
const {
|
||||
data: customParameterData,
|
||||
loading: customParameterLoading,
|
||||
error: customParameterError,
|
||||
} = useQuery<UserCustomParameterResponse>(getCustomParameter);
|
||||
const [allowToRender, setAllowToRender] = React.useState(false);
|
||||
const sendToServer = useCallback((data: Array<{[x: string]: string}>, count = 0) => {
|
||||
Meteor.callAsync('addUserSettings', data).then(() => {
|
||||
setAllowToRender(true);
|
||||
})
|
||||
.catch(() => {
|
||||
if (count < 3) {
|
||||
setTimeout(() => {
|
||||
sendToServer(data, count + 1);
|
||||
}, 500);
|
||||
} else {
|
||||
throw new Error('Error on sending user settings to server');
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
if (customParameterData && !customParameterLoading) {
|
||||
const filteredData = customParameterData.user_customParameter.map((uc) => {
|
||||
const { parameter, value } = uc;
|
||||
return { [parameter]: value };
|
||||
});
|
||||
sendToServer(filteredData);
|
||||
}
|
||||
}, [
|
||||
customParameterData,
|
||||
customParameterLoading,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (customParameterError) {
|
||||
throw new Error(`Error on requesting custom parameter data: ${customParameterError}`);
|
||||
}
|
||||
}, [customParameterError]);
|
||||
|
||||
return allowToRender ? <>{children}</> : null;
|
||||
};
|
||||
|
||||
export default CustomUsersSettings;
|
@ -0,0 +1,23 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
interface CustomParameter {
|
||||
parameter: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface UserCustomParameterResponse {
|
||||
user_customParameter: CustomParameter[];
|
||||
}
|
||||
|
||||
export const getCustomParameter = gql`
|
||||
query getCustomParameter {
|
||||
user_customParameter {
|
||||
parameter
|
||||
value
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default {
|
||||
getCustomParameter,
|
||||
};
|
@ -0,0 +1,215 @@
|
||||
import { useMutation, useQuery, useSubscription } from '@apollo/client';
|
||||
import React, { useContext, useEffect } from 'react';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
// @ts-ignore - type avaible only to server package
|
||||
import { DDP } from 'meteor/ddp-client';
|
||||
import { Session } from 'meteor/session';
|
||||
import {
|
||||
getUserCurrent,
|
||||
GetUserCurrentResponse,
|
||||
getUserInfo,
|
||||
GetUserInfoResponse,
|
||||
userJoinMutation,
|
||||
} from './queries';
|
||||
import { setAuthData } from '/imports/ui/core/local-states/useAuthData';
|
||||
import MeetingEndedContainer from '../../meeting-ended/meeting-ended-ts/component';
|
||||
import { setUserDataToSessionStorage } from './service';
|
||||
import { LoadingContext } from '../../common/loading-screen/loading-screen-HOC/component';
|
||||
|
||||
const connectionTimeout = 60000;
|
||||
|
||||
interface PresenceManagerContainerProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
interface PresenceManagerProps extends PresenceManagerContainerProps {
|
||||
authToken: string;
|
||||
logoutUrl: string;
|
||||
meetingId: string;
|
||||
meetingName: string;
|
||||
userName: string;
|
||||
extId: string;
|
||||
userId: string;
|
||||
joinErrorCode: string;
|
||||
joinErrorMessage: string;
|
||||
joined: boolean;
|
||||
meetingEnded: boolean;
|
||||
endedReasonCode: string;
|
||||
endedBy: string;
|
||||
ejectReasonCode: string;
|
||||
bannerColor: string;
|
||||
bannerText: string;
|
||||
}
|
||||
|
||||
const PresenceManager: React.FC<PresenceManagerProps> = ({
|
||||
authToken,
|
||||
children,
|
||||
logoutUrl,
|
||||
meetingId,
|
||||
meetingName,
|
||||
userName,
|
||||
extId,
|
||||
userId,
|
||||
joinErrorCode,
|
||||
joinErrorMessage,
|
||||
joined,
|
||||
meetingEnded,
|
||||
endedReasonCode,
|
||||
endedBy,
|
||||
ejectReasonCode,
|
||||
bannerColor,
|
||||
bannerText,
|
||||
}) => {
|
||||
const [allowToRender, setAllowToRender] = React.useState(false);
|
||||
const [dispatchUserJoin] = useMutation(userJoinMutation);
|
||||
const timeoutRef = React.useRef<ReturnType<typeof setTimeout>>();
|
||||
const loadingContextInfo = useContext(LoadingContext);
|
||||
|
||||
useEffect(() => {
|
||||
timeoutRef.current = setTimeout(() => {
|
||||
loadingContextInfo.setLoading(false, '');
|
||||
throw new Error('Authentication timeout');
|
||||
}, connectionTimeout);
|
||||
|
||||
DDP.onReconnect(() => {
|
||||
Meteor.callAsync('validateConnection', authToken, meetingId, userId);
|
||||
});
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const sessionToken = urlParams.get('sessionToken') as string;
|
||||
setAuthData({
|
||||
meetingId,
|
||||
userId,
|
||||
authToken,
|
||||
logoutUrl,
|
||||
sessionToken,
|
||||
userName,
|
||||
extId,
|
||||
meetingName,
|
||||
});
|
||||
setUserDataToSessionStorage({
|
||||
meetingId,
|
||||
userId,
|
||||
authToken,
|
||||
logoutUrl,
|
||||
sessionToken,
|
||||
userName,
|
||||
extId,
|
||||
meetingName,
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (bannerColor || bannerText) {
|
||||
Session.set('bannerText', bannerText);
|
||||
Session.set('bannerColor', bannerColor);
|
||||
}
|
||||
}, [bannerColor, bannerText]);
|
||||
|
||||
useEffect(() => {
|
||||
if (authToken && !joined) {
|
||||
dispatchUserJoin({
|
||||
variables: {
|
||||
authToken,
|
||||
clientType: 'HTML5',
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [joined, authToken]);
|
||||
|
||||
useEffect(() => {
|
||||
if (joined) {
|
||||
clearTimeout(timeoutRef.current);
|
||||
Meteor.callAsync('validateConnection', authToken, meetingId, userId).then(() => {
|
||||
setAllowToRender(true);
|
||||
});
|
||||
}
|
||||
}, [joined]);
|
||||
|
||||
useEffect(() => {
|
||||
if (joinErrorCode) {
|
||||
loadingContextInfo.setLoading(false, '');
|
||||
}
|
||||
},
|
||||
[joinErrorCode, joinErrorMessage]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{allowToRender && !(meetingEnded || joinErrorCode || ejectReasonCode) ? children : null}
|
||||
{
|
||||
meetingEnded || joinErrorCode || ejectReasonCode
|
||||
? (
|
||||
<MeetingEndedContainer
|
||||
meetingEndedCode={endedReasonCode}
|
||||
endedBy={endedBy}
|
||||
joinErrorCode={joinErrorCode || ejectReasonCode}
|
||||
/>
|
||||
)
|
||||
: null
|
||||
}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const PresenceManagerContainer: React.FC<PresenceManagerContainerProps> = ({ children }) => {
|
||||
const { loading, error, data } = useSubscription<GetUserCurrentResponse>(getUserCurrent);
|
||||
|
||||
const {
|
||||
loading: userInfoLoading,
|
||||
error: userInfoError,
|
||||
data: userInfoData,
|
||||
} = useQuery<GetUserInfoResponse>(getUserInfo);
|
||||
|
||||
const loadingContextInfo = useContext(LoadingContext);
|
||||
if (loading || userInfoLoading) return null;
|
||||
if (error || userInfoError) {
|
||||
loadingContextInfo.setLoading(false, '');
|
||||
throw new Error('Error on user authentication: ', error);
|
||||
}
|
||||
|
||||
if (!data || data.user_current.length === 0) return null;
|
||||
if (!userInfoData
|
||||
|| userInfoData.meeting.length === 0
|
||||
|| userInfoData.user_current.length === 0) return null;
|
||||
const {
|
||||
authToken,
|
||||
joinErrorCode,
|
||||
joinErrorMessage,
|
||||
joined,
|
||||
ejectReasonCode,
|
||||
meeting,
|
||||
} = data.user_current[0];
|
||||
const {
|
||||
logoutUrl,
|
||||
meetingId,
|
||||
name: meetingName,
|
||||
bannerColor,
|
||||
bannerText,
|
||||
} = userInfoData.meeting[0];
|
||||
const { extId, name: userName, userId } = userInfoData.user_current[0];
|
||||
|
||||
return (
|
||||
<PresenceManager
|
||||
authToken={authToken}
|
||||
logoutUrl={logoutUrl}
|
||||
meetingId={meetingId}
|
||||
meetingName={meetingName}
|
||||
userName={userName}
|
||||
extId={extId}
|
||||
userId={userId}
|
||||
joined={joined}
|
||||
joinErrorCode={joinErrorCode}
|
||||
joinErrorMessage={joinErrorMessage}
|
||||
meetingEnded={meeting.ended}
|
||||
endedReasonCode={meeting.endedReasonCode}
|
||||
endedBy={meeting.endedBy}
|
||||
ejectReasonCode={ejectReasonCode}
|
||||
bannerColor={bannerColor}
|
||||
bannerText={bannerText}
|
||||
>
|
||||
{children}
|
||||
</PresenceManager>
|
||||
);
|
||||
};
|
||||
|
||||
export default PresenceManagerContainer;
|
@ -0,0 +1,80 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export interface GetUserCurrentResponse {
|
||||
user_current: Array<{
|
||||
userId: string;
|
||||
authToken: string;
|
||||
joined: boolean;
|
||||
joinErrorCode: string;
|
||||
joinErrorMessage: string;
|
||||
ejectReasonCode: string;
|
||||
meeting: {
|
||||
ended: boolean;
|
||||
endedReasonCode: string;
|
||||
endedBy: string;
|
||||
};
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface GetUserInfoResponse {
|
||||
meeting: Array<{
|
||||
meetingId: string;
|
||||
name: string;
|
||||
logoutUrl: string;
|
||||
bannerColor: string;
|
||||
bannerText: string;
|
||||
}>;
|
||||
user_current: Array<{
|
||||
extId: string;
|
||||
name: string;
|
||||
userId: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export const getUserInfo = gql`
|
||||
query getUserInfo {
|
||||
meeting {
|
||||
meetingId
|
||||
name
|
||||
logoutUrl
|
||||
bannerColor
|
||||
bannerText
|
||||
}
|
||||
user_current {
|
||||
extId
|
||||
name
|
||||
userId
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const getUserCurrent = gql`
|
||||
subscription getUserCurrent {
|
||||
user_current {
|
||||
userId
|
||||
authToken
|
||||
joinErrorCode
|
||||
joinErrorMessage
|
||||
joined
|
||||
ejectReasonCode
|
||||
meeting {
|
||||
ended
|
||||
endedReasonCode
|
||||
endedBy
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const userJoinMutation = gql`
|
||||
mutation UserJoin($authToken: String!, $clientType: String!) {
|
||||
userJoinMeeting(
|
||||
authToken: $authToken,
|
||||
clientType: $clientType,
|
||||
)
|
||||
}
|
||||
`;
|
||||
export default {
|
||||
getUserCurrent,
|
||||
userJoinMutation,
|
||||
getUserInfo,
|
||||
};
|
@ -0,0 +1,36 @@
|
||||
export const JoinErrorCodeTable = {
|
||||
NOT_EJECT: 'not_eject_reason',
|
||||
DUPLICATE_USER: 'duplicate_user_in_meeting_eject_reason',
|
||||
PERMISSION_FAILED: 'not_enough_permission_eject_reason',
|
||||
EJECT_USER: 'user_requested_eject_reason',
|
||||
SYSTEM_EJECT_USER: 'system_requested_eject_reason',
|
||||
VALIDATE_TOKEN: 'validate_token_failed_eject_reason',
|
||||
USER_INACTIVITY: 'user_inactivity_eject_reason',
|
||||
BANNED_USER_REJOINING: 'banned_user_rejoining_reason',
|
||||
USER_LOGGED_OUT: 'user_logged_out_reason',
|
||||
MAX_PARTICIPANTS: 'max_participants_reason',
|
||||
};
|
||||
|
||||
export const setUserDataToSessionStorage = (userData: {
|
||||
meetingId: string,
|
||||
userId: string,
|
||||
authToken: string,
|
||||
logoutUrl: string,
|
||||
sessionToken: string,
|
||||
userName: string,
|
||||
extId: string,
|
||||
meetingName: string,
|
||||
}) => {
|
||||
sessionStorage.setItem('meetingId', userData.meetingId);
|
||||
sessionStorage.setItem('userId', userData.userId);
|
||||
sessionStorage.setItem('logoutUrl', userData.logoutUrl);
|
||||
sessionStorage.setItem('sessionToken', userData.sessionToken);
|
||||
sessionStorage.setItem('userName', userData.userName);
|
||||
sessionStorage.setItem('extId', userData.extId);
|
||||
sessionStorage.setItem('meetingName', userData.meetingName);
|
||||
};
|
||||
|
||||
export default {
|
||||
JoinErrorCodeTable,
|
||||
setUserDataToSessionStorage,
|
||||
};
|
@ -1,7 +1,7 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { LAYOUT_TYPE, CAMERADOCK_POSITION, PANELS } from './enums';
|
||||
|
||||
const CHAT_CONFIG = Meteor.settings.public.chat;
|
||||
const CHAT_CONFIG = window.meetingClientSettings.public.chat;
|
||||
const PUBLIC_CHAT_ID = CHAT_CONFIG.public_id;
|
||||
|
||||
const DEFAULT_VALUES = {
|
||||
|
@ -22,7 +22,7 @@ const LayoutModalComponent = (props) => {
|
||||
|
||||
const [selectedLayout, setSelectedLayout] = useState(application.selectedLayout);
|
||||
|
||||
const BASE_NAME = Meteor.settings.public.app.basename;
|
||||
const BASE_NAME = window.meetingClientSettings.public.app.basename;
|
||||
|
||||
const LAYOUTS_PATH = `${BASE_NAME}/resources/images/layouts/`;
|
||||
const isKeepPushingLayoutEnabled = SettingsService.isKeepPushingLayoutEnabled();
|
||||
|
@ -9,7 +9,7 @@ import { isMobile } from '../utils';
|
||||
import { updateSettings } from '/imports/ui/components/settings/service';
|
||||
import { Session } from 'meteor/session';
|
||||
|
||||
const HIDE_PRESENTATION = Meteor.settings.public.layout.hidePresentationOnJoin;
|
||||
const HIDE_PRESENTATION = window.meetingClientSettings.public.layout.hidePresentationOnJoin;
|
||||
|
||||
const equalDouble = (n1, n2) => {
|
||||
const precision = 0.01;
|
||||
|
@ -2,7 +2,7 @@ import Users from '/imports/api/users';
|
||||
import Auth from '/imports/ui/services/auth';
|
||||
import Meetings from '/imports/api/meetings';
|
||||
|
||||
const ROLE_MODERATOR = Meteor.settings.public.user.role_moderator;
|
||||
const ROLE_MODERATOR = window.meetingClientSettings.public.user.role_moderator;
|
||||
|
||||
const isModerator = () => {
|
||||
const user = Users.findOne(
|
||||
@ -40,7 +40,7 @@ const setLearningDashboardCookie = () => {
|
||||
};
|
||||
|
||||
const openLearningDashboardUrl = (lang) => {
|
||||
const APP = Meteor.settings.public.app;
|
||||
const APP = window.meetingClientSettings.public.app;
|
||||
if (getLearningDashboardAccessToken() && setLearningDashboardCookie()) {
|
||||
window.open(`${APP.learningDashboardBase}/?meeting=${Auth.meetingID}&lang=${lang}`, '_blank');
|
||||
} else {
|
||||
|
@ -70,8 +70,8 @@ const FETCHING = 'fetching';
|
||||
const FALLBACK = 'fallback';
|
||||
const READY = 'ready';
|
||||
const supportedBrowsers = ['Chrome', 'Firefox', 'Safari', 'Opera', 'Microsoft Edge', 'Yandex Browser'];
|
||||
const DEFAULT_LANGUAGE = Meteor.settings.public.app.defaultSettings.application.fallbackLocale;
|
||||
const CLIENT_VERSION = Meteor.settings.public.app.html5ClientBuild;
|
||||
const DEFAULT_LANGUAGE = window.meetingClientSettings.public.app.defaultSettings.application.fallbackLocale;
|
||||
const CLIENT_VERSION = window.meetingClientSettings.public.app.html5ClientBuild;
|
||||
|
||||
export default class Legacy extends Component {
|
||||
constructor(props) {
|
||||
|
@ -5,7 +5,7 @@ import { LockStruct } from './context';
|
||||
import Users from '/imports/api/users';
|
||||
import { withLockContext } from './withContext';
|
||||
|
||||
const ROLE_MODERATOR = Meteor.settings.public.user.role_moderator;
|
||||
const ROLE_MODERATOR = window.meetingClientSettings.public.user.role_moderator;
|
||||
|
||||
const lockContextContainer = (component) => withTracker(() => {
|
||||
const lockSetting = new LockStruct();
|
||||
|
@ -10,9 +10,9 @@ import NotesService from '/imports/ui/components/notes/service';
|
||||
import VideoStreams from '/imports/api/video-streams';
|
||||
import Auth from '/imports/ui/services/auth/index';
|
||||
|
||||
const LAYOUT_CONFIG = Meteor.settings.public.layout;
|
||||
const KURENTO_CONFIG = Meteor.settings.public.kurento;
|
||||
const PRESENTATION_CONFIG = Meteor.settings.public.presentation;
|
||||
const LAYOUT_CONFIG = window.meetingClientSettings.public.layout;
|
||||
const KURENTO_CONFIG = window.meetingClientSettings.public.kurento;
|
||||
const PRESENTATION_CONFIG = window.meetingClientSettings.public.presentation;
|
||||
|
||||
function shouldShowWhiteboard() {
|
||||
return true;
|
||||
|
@ -176,7 +176,7 @@ class MeetingEnded extends PureComponent {
|
||||
|
||||
shouldShowFeedback() {
|
||||
const { dispatched } = this.state;
|
||||
return getFromUserSettings('bbb_ask_for_feedback_on_logout', Meteor.settings.public.app.askForFeedbackOnLogout) && !dispatched;
|
||||
return getFromUserSettings('bbb_ask_for_feedback_on_logout', window.meetingClientSettings.public.app.askForFeedbackOnLogout) && !dispatched;
|
||||
}
|
||||
|
||||
confirmRedirect() {
|
||||
|
@ -0,0 +1,451 @@
|
||||
import React, {
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import { isEmpty } from 'radash';
|
||||
import { useQuery } from '@apollo/client';
|
||||
import {
|
||||
JoinErrorCodeTable,
|
||||
MeetingEndedTable,
|
||||
openLearningDashboardUrl,
|
||||
setLearningDashboardCookie,
|
||||
} from './service';
|
||||
import { MeetingEndDataResponse, getMeetingEndData } from './queries';
|
||||
import useAuthData from '/imports/ui/core/local-states/useAuthData';
|
||||
import Icon from '/imports/ui/components/common/icon/icon-ts/component';
|
||||
import Styled from './styles';
|
||||
import Rating from '../rating/component';
|
||||
import { LoadingContext } from '../../common/loading-screen/loading-screen-HOC/component';
|
||||
import logger from '/imports/startup/client/logger';
|
||||
|
||||
const intlMessage = defineMessages({
|
||||
410: {
|
||||
id: 'app.meeting.ended',
|
||||
description: 'message when meeting is ended',
|
||||
},
|
||||
403: {
|
||||
id: 'app.error.removed',
|
||||
description: 'Message to display when user is removed from the conference',
|
||||
},
|
||||
430: {
|
||||
id: 'app.error.meeting.ended',
|
||||
description: 'user logged conference',
|
||||
},
|
||||
'acl-not-allowed': {
|
||||
id: 'app.error.removed',
|
||||
description: 'Message to display when user is removed from the conference',
|
||||
},
|
||||
messageEnded: {
|
||||
id: 'app.meeting.endedMessage',
|
||||
description: 'message saying to go back to home screen',
|
||||
},
|
||||
messageEndedByUser: {
|
||||
id: 'app.meeting.endedByUserMessage',
|
||||
description: 'message informing who ended the meeting',
|
||||
},
|
||||
messageEndedByNoModeratorSingular: {
|
||||
id: 'app.meeting.endedByNoModeratorMessageSingular',
|
||||
description: 'message informing that the meeting was ended due to no moderator present (singular)',
|
||||
},
|
||||
messageEndedByNoModeratorPlural: {
|
||||
id: 'app.meeting.endedByNoModeratorMessagePlural',
|
||||
description: 'message informing that the meeting was ended due to no moderator present (plural)',
|
||||
},
|
||||
buttonOkay: {
|
||||
id: 'app.meeting.endNotification.ok.label',
|
||||
description: 'label okay for button',
|
||||
},
|
||||
title: {
|
||||
id: 'app.feedback.title',
|
||||
description: 'title for feedback screen',
|
||||
},
|
||||
subtitle: {
|
||||
id: 'app.feedback.subtitle',
|
||||
description: 'subtitle for feedback screen',
|
||||
},
|
||||
textarea: {
|
||||
id: 'app.feedback.textarea',
|
||||
description: 'placeholder for textarea',
|
||||
},
|
||||
confirmDesc: {
|
||||
id: 'app.leaveConfirmation.confirmDesc',
|
||||
description: 'adds context to confim option',
|
||||
},
|
||||
sendLabel: {
|
||||
id: 'app.feedback.sendFeedback',
|
||||
description: 'send feedback button label',
|
||||
},
|
||||
sendDesc: {
|
||||
id: 'app.feedback.sendFeedbackDesc',
|
||||
description: 'adds context to send feedback option',
|
||||
},
|
||||
[JoinErrorCodeTable.DUPLICATE_USER]: {
|
||||
id: 'app.meeting.logout.duplicateUserEjectReason',
|
||||
description: 'message for duplicate users',
|
||||
},
|
||||
[JoinErrorCodeTable.PERMISSION_FAILED]: {
|
||||
id: 'app.meeting.logout.permissionEjectReason',
|
||||
description: 'message for whom was kicked by doing something without permission',
|
||||
},
|
||||
[JoinErrorCodeTable.EJECT_USER]: {
|
||||
id: 'app.meeting.logout.ejectedFromMeeting',
|
||||
description: 'message when the user is removed by someone',
|
||||
},
|
||||
[JoinErrorCodeTable.SYSTEM_EJECT_USER]: {
|
||||
id: 'app.meeting.logout.ejectedFromMeeting',
|
||||
description: 'message when the user is removed by the system',
|
||||
},
|
||||
[JoinErrorCodeTable.MAX_PARTICIPANTS]: {
|
||||
id: 'app.meeting.logout.maxParticipantsReached',
|
||||
description: 'message when the user is rejected due to max participants limit',
|
||||
},
|
||||
[JoinErrorCodeTable.VALIDATE_TOKEN]: {
|
||||
id: 'app.meeting.logout.validateTokenFailedEjectReason',
|
||||
description: 'invalid auth token',
|
||||
},
|
||||
[JoinErrorCodeTable.USER_INACTIVITY]: {
|
||||
id: 'app.meeting.logout.userInactivityEjectReason',
|
||||
description: 'message to whom was kicked by inactivity',
|
||||
},
|
||||
[JoinErrorCodeTable.USER_LOGGED_OUT]: {
|
||||
id: 'app.feedback.title',
|
||||
description: 'message to whom was kicked by logging out',
|
||||
},
|
||||
[JoinErrorCodeTable.BANNED_USER_REJOINING]: {
|
||||
id: 'app.error.userBanned',
|
||||
description: 'message to whom was banned',
|
||||
},
|
||||
open_activity_report_btn: {
|
||||
id: 'app.learning-dashboard.clickHereToOpen',
|
||||
description: 'description of link to open activity report',
|
||||
},
|
||||
[MeetingEndedTable.ENDED_FROM_API]: {
|
||||
id: 'app.meeting.endedFromAPI',
|
||||
description: '',
|
||||
},
|
||||
[MeetingEndedTable.ENDED_WHEN_NOT_JOINED]: {
|
||||
id: 'app.meeting.endedWhenNoUserJoined',
|
||||
description: '',
|
||||
},
|
||||
[MeetingEndedTable.ENDED_WHEN_LAST_USER_LEFT]: {
|
||||
id: 'app.meeting.endedWhenLastUserLeft',
|
||||
description: '',
|
||||
},
|
||||
[MeetingEndedTable.ENDED_AFTER_USER_LOGGED_OUT]: {
|
||||
id: 'app.meeting.endedWhenLastUserLeft',
|
||||
description: '',
|
||||
},
|
||||
[MeetingEndedTable.ENDED_AFTER_EXCEEDING_DURATION]: {
|
||||
id: 'app.meeting.endedAfterExceedingDuration',
|
||||
description: '',
|
||||
},
|
||||
[MeetingEndedTable.BREAKOUT_ENDED_EXCEEDING_DURATION]: {
|
||||
id: 'app.meeting.breakoutEndedAfterExceedingDuration',
|
||||
description: '',
|
||||
},
|
||||
[MeetingEndedTable.BREAKOUT_ENDED_BY_MOD]: {
|
||||
id: 'app.meeting.breakoutEndedByModerator',
|
||||
description: '',
|
||||
},
|
||||
[MeetingEndedTable.ENDED_DUE_TO_NO_AUTHED_USER]: {
|
||||
id: 'app.meeting.endedDueNoAuthed',
|
||||
description: '',
|
||||
},
|
||||
[MeetingEndedTable.ENDED_DUE_TO_NO_MODERATOR]: {
|
||||
id: 'app.meeting.endedDueNoModerators',
|
||||
description: '',
|
||||
},
|
||||
});
|
||||
|
||||
interface MeetingEndedContainerProps {
|
||||
endedBy: string;
|
||||
meetingEndedCode: string;
|
||||
joinErrorCode: string;
|
||||
}
|
||||
|
||||
interface MeetingEndedProps extends MeetingEndedContainerProps {
|
||||
allowDefaultLogoutUrl: boolean;
|
||||
askForFeedbackOnLogout: boolean
|
||||
learningDashboardAccessToken: string;
|
||||
role: string;
|
||||
learningDashboardBase: string;
|
||||
isBreakout: boolean;
|
||||
}
|
||||
|
||||
const MeetingEnded: React.FC<MeetingEndedProps> = ({
|
||||
endedBy,
|
||||
joinErrorCode,
|
||||
meetingEndedCode,
|
||||
allowDefaultLogoutUrl,
|
||||
askForFeedbackOnLogout,
|
||||
learningDashboardAccessToken,
|
||||
role,
|
||||
learningDashboardBase,
|
||||
isBreakout,
|
||||
}) => {
|
||||
const loadingContextInfo = useContext(LoadingContext);
|
||||
const intl = useIntl();
|
||||
const [{
|
||||
authToken,
|
||||
meetingId,
|
||||
logoutUrl,
|
||||
userName,
|
||||
userId,
|
||||
}] = useAuthData();
|
||||
const [selectedStars, setSelectedStars] = useState(0);
|
||||
const [dispatched, setDispatched] = useState(false);
|
||||
|
||||
const generateEndMessage = useCallback((joinErrorCode: string, meetingEndedCode: string, endedBy: string) => {
|
||||
if (!isEmpty(endedBy)) {
|
||||
return intl.formatMessage(intlMessage.messageEndedByUser, { 0: endedBy });
|
||||
}
|
||||
// OR opetaror always returns the first truthy value
|
||||
|
||||
const code = meetingEndedCode || joinErrorCode || '410';
|
||||
return intl.formatMessage(intlMessage[code]);
|
||||
}, []);
|
||||
|
||||
const sendFeedback = useCallback(() => {
|
||||
const textarea = document.getElementById('feedbackComment') as HTMLTextAreaElement;
|
||||
const comment = (textarea?.value || '').trim();
|
||||
|
||||
const message = {
|
||||
rating: selectedStars,
|
||||
userId,
|
||||
userName,
|
||||
authToken,
|
||||
meetingId,
|
||||
comment,
|
||||
userRole: role,
|
||||
};
|
||||
const url = './feedback';
|
||||
const options = {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(message),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
};
|
||||
setDispatched(true);
|
||||
|
||||
fetch(url, options).then(() => {
|
||||
if (role === 'VIEWER') {
|
||||
const REDIRECT_WAIT_TIME = 5000;
|
||||
setTimeout(() => {
|
||||
window.location.href = logoutUrl;
|
||||
}, REDIRECT_WAIT_TIME);
|
||||
}
|
||||
}).catch((e) => {
|
||||
logger.warn({
|
||||
logCode: 'user_feedback_not_sent_error',
|
||||
extraInfo: {
|
||||
errorName: e.name,
|
||||
errorMessage: e.message,
|
||||
},
|
||||
}, `Unable to send feedback: ${e.message}`);
|
||||
});
|
||||
}, [selectedStars]);
|
||||
|
||||
const confirmRedirect = (isBreakout: boolean, allowRedirect: boolean) => {
|
||||
if (isBreakout) window.close();
|
||||
if (allowRedirect) {
|
||||
window.location.href = logoutUrl;
|
||||
}
|
||||
};
|
||||
|
||||
const logoutButton = useMemo(() => {
|
||||
const { locale } = intl;
|
||||
return (
|
||||
(
|
||||
<Styled.Wrapper>
|
||||
{
|
||||
learningDashboardAccessToken && role === 'moderator'
|
||||
// Always set cookie in case Dashboard is already opened
|
||||
&& setLearningDashboardCookie(learningDashboardAccessToken, meetingId) === true
|
||||
? (
|
||||
<Styled.Text>
|
||||
<Styled.MeetingEndedButton
|
||||
color="default"
|
||||
onClick={() => openLearningDashboardUrl(learningDashboardAccessToken,
|
||||
meetingId,
|
||||
authToken,
|
||||
learningDashboardBase,
|
||||
locale)}
|
||||
aria-description={intl.formatMessage(intlMessage.open_activity_report_btn)}
|
||||
>
|
||||
<Icon
|
||||
iconName="multi_whiteboard"
|
||||
/>
|
||||
</Styled.MeetingEndedButton>
|
||||
</Styled.Text>
|
||||
) : null
|
||||
}
|
||||
<Styled.Text>
|
||||
{intl.formatMessage(intlMessage.messageEnded)}
|
||||
</Styled.Text>
|
||||
|
||||
<Styled.MeetingEndedButton
|
||||
color="primary"
|
||||
onClick={() => confirmRedirect(isBreakout, allowDefaultLogoutUrl)}
|
||||
aria-description={intl.formatMessage(intlMessage.confirmDesc)}
|
||||
>
|
||||
{intl.formatMessage(intlMessage.buttonOkay)}
|
||||
</Styled.MeetingEndedButton>
|
||||
</Styled.Wrapper>
|
||||
)
|
||||
);
|
||||
}, [learningDashboardAccessToken, role, meetingId, authToken, learningDashboardBase]);
|
||||
|
||||
const feedbackScreen = useMemo(() => {
|
||||
const shouldShowFeedback = askForFeedbackOnLogout && !dispatched;
|
||||
const noRating = selectedStars === 0;
|
||||
return (
|
||||
<>
|
||||
<Styled.Text>
|
||||
{shouldShowFeedback
|
||||
? intl.formatMessage(intlMessage.subtitle)
|
||||
: intl.formatMessage(intlMessage.messageEnded)}
|
||||
</Styled.Text>
|
||||
|
||||
{shouldShowFeedback ? (
|
||||
<div data-test="rating">
|
||||
<Rating
|
||||
total="5"
|
||||
onRate={setSelectedStars}
|
||||
/>
|
||||
{!noRating ? (
|
||||
<Styled.TextArea
|
||||
rows={5}
|
||||
id="feedbackComment"
|
||||
placeholder={intl.formatMessage(intlMessage.textarea)}
|
||||
aria-describedby="textareaDesc"
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
{noRating ? (
|
||||
<Styled.MeetingEndedButton
|
||||
color="primary"
|
||||
onClick={() => setDispatched(true)}
|
||||
aria-description={intl.formatMessage(intlMessage.confirmDesc)}
|
||||
>
|
||||
{intl.formatMessage(intlMessage.buttonOkay)}
|
||||
</Styled.MeetingEndedButton>
|
||||
) : null}
|
||||
{!noRating ? (
|
||||
<Styled.MeetingEndedButton
|
||||
onClick={sendFeedback}
|
||||
aria-description={intl.formatMessage(intlMessage.sendDesc)}
|
||||
>
|
||||
{intl.formatMessage(intlMessage.sendLabel)}
|
||||
</Styled.MeetingEndedButton>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
loadingContextInfo.setLoading(false, '');
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Styled.Parent>
|
||||
<Styled.Modal data-test="meetingEndedModal">
|
||||
<Styled.Content>
|
||||
<Styled.Title>
|
||||
{generateEndMessage(joinErrorCode, meetingEndedCode, endedBy)}
|
||||
</Styled.Title>
|
||||
{allowDefaultLogoutUrl && !askForFeedbackOnLogout ? logoutButton : null}
|
||||
{askForFeedbackOnLogout ? feedbackScreen : null}
|
||||
</Styled.Content>
|
||||
</Styled.Modal>
|
||||
</Styled.Parent>
|
||||
);
|
||||
};
|
||||
|
||||
const MeetingEndedContainer: React.FC<MeetingEndedContainerProps> = ({
|
||||
endedBy,
|
||||
meetingEndedCode,
|
||||
joinErrorCode,
|
||||
}) => {
|
||||
const {
|
||||
loading: meetingEndLoading,
|
||||
error: meetingEndError,
|
||||
data: meetingEndData,
|
||||
} = useQuery<MeetingEndDataResponse>(getMeetingEndData);
|
||||
|
||||
if (meetingEndLoading || !meetingEndData) {
|
||||
return (
|
||||
<MeetingEnded
|
||||
endedBy=""
|
||||
joinErrorCode=""
|
||||
meetingEndedCode=""
|
||||
allowDefaultLogoutUrl={false}
|
||||
askForFeedbackOnLogout={false}
|
||||
learningDashboardAccessToken=""
|
||||
// eslint-disable-next-line jsx-a11y/aria-role
|
||||
role=""
|
||||
learningDashboardBase=""
|
||||
isBreakout={false}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (meetingEndError) {
|
||||
logger.error('Error on fetching meeting end data: ', meetingEndError);
|
||||
return (
|
||||
<MeetingEnded
|
||||
endedBy=""
|
||||
joinErrorCode=""
|
||||
meetingEndedCode=""
|
||||
allowDefaultLogoutUrl={false}
|
||||
askForFeedbackOnLogout={false}
|
||||
learningDashboardAccessToken=""
|
||||
// eslint-disable-next-line jsx-a11y/aria-role
|
||||
role=""
|
||||
learningDashboardBase=""
|
||||
isBreakout={false}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const {
|
||||
user_current,
|
||||
} = meetingEndData;
|
||||
const {
|
||||
role,
|
||||
meeting,
|
||||
} = user_current[0];
|
||||
|
||||
const {
|
||||
learningDashboard,
|
||||
isBreakout,
|
||||
clientSettings,
|
||||
} = meeting;
|
||||
|
||||
const {
|
||||
askForFeedbackOnLogout,
|
||||
allowDefaultLogoutUrl,
|
||||
learningDashboardBase,
|
||||
} = clientSettings;
|
||||
|
||||
return (
|
||||
<MeetingEnded
|
||||
endedBy={endedBy}
|
||||
joinErrorCode={joinErrorCode}
|
||||
meetingEndedCode={meetingEndedCode}
|
||||
allowDefaultLogoutUrl={allowDefaultLogoutUrl}
|
||||
askForFeedbackOnLogout={askForFeedbackOnLogout}
|
||||
learningDashboardAccessToken={learningDashboard?.learningDashboardAccessToken}
|
||||
role={role}
|
||||
learningDashboardBase={learningDashboardBase}
|
||||
isBreakout={isBreakout}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default MeetingEndedContainer;
|
@ -0,0 +1,43 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export interface MeetingEndDataResponse {
|
||||
user_current: Array<{
|
||||
role: string;
|
||||
meeting: {
|
||||
learningDashboard: {
|
||||
learningDashboardAccessToken: string;
|
||||
}
|
||||
isBreakout: boolean;
|
||||
logoutUrl: string;
|
||||
clientSettings: {
|
||||
askForFeedbackOnLogout: boolean;
|
||||
allowDefaultLogoutUrl: boolean;
|
||||
learningDashboardBase: string;
|
||||
};
|
||||
};
|
||||
}>;
|
||||
}
|
||||
|
||||
export const getMeetingEndData = gql`
|
||||
query getMeetingEndData {
|
||||
user_current {
|
||||
role
|
||||
meeting {
|
||||
learningDashboard {
|
||||
learningDashboardAccessToken
|
||||
}
|
||||
isBreakout
|
||||
logoutUrl
|
||||
clientSettings {
|
||||
askForFeedbackOnLogout: clientSettingsJson(path: "$.public.app.askForFeedbackOnLogout")
|
||||
allowDefaultLogoutUrl: clientSettingsJson(path: "$.public.app.allowDefaultLogoutUrl")
|
||||
learningDashboardBase: clientSettingsJson(path: "$.public.app.learningDashboardBase")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default {
|
||||
getMeetingEndData,
|
||||
};
|
@ -0,0 +1,55 @@
|
||||
export const JoinErrorCodeTable = {
|
||||
NOT_EJECT: 'not_eject_reason',
|
||||
DUPLICATE_USER: 'duplicate_user_in_meeting_eject_reason',
|
||||
PERMISSION_FAILED: 'not_enough_permission_eject_reason',
|
||||
EJECT_USER: 'user_requested_eject_reason',
|
||||
SYSTEM_EJECT_USER: 'system_requested_eject_reason',
|
||||
VALIDATE_TOKEN: 'validate_token_failed_eject_reason',
|
||||
USER_INACTIVITY: 'user_inactivity_eject_reason',
|
||||
BANNED_USER_REJOINING: 'banned_user_rejoining_reason',
|
||||
USER_LOGGED_OUT: 'user_logged_out_reason',
|
||||
MAX_PARTICIPANTS: 'max_participants_reason',
|
||||
};
|
||||
|
||||
export const MeetingEndedTable = {
|
||||
ENDED_FROM_API: 'ENDED_FROM_API',
|
||||
ENDED_WHEN_NOT_JOINED: 'ENDED_WHEN_NOT_JOINED',
|
||||
ENDED_WHEN_LAST_USER_LEFT: 'ENDED_WHEN_LAST_USER_LEFT',
|
||||
ENDED_AFTER_USER_LOGGED_OUT: 'ENDED_AFTER_USER_LOGGED_OUT',
|
||||
ENDED_AFTER_EXCEEDING_DURATION: 'ENDED_AFTER_EXCEEDING_DURATION',
|
||||
BREAKOUT_ENDED_EXCEEDING_DURATION: 'BREAKOUT_ENDED_EXCEEDING_DURATION',
|
||||
BREAKOUT_ENDED_BY_MOD: 'BREAKOUT_ENDED_BY_MOD',
|
||||
ENDED_DUE_TO_NO_AUTHED_USER: 'ENDED_DUE_TO_NO_AUTHED_USER',
|
||||
ENDED_DUE_TO_NO_MODERATOR: 'ENDED_DUE_TO_NO_MODERATOR',
|
||||
};
|
||||
|
||||
export const openLearningDashboardUrl = (
|
||||
accessToken: string,
|
||||
mId: string,
|
||||
sToken:string,
|
||||
learningDashboardBase: string,
|
||||
lang: string,
|
||||
) => {
|
||||
if (accessToken && setLearningDashboardCookie(accessToken, mId)) {
|
||||
window.open(`${learningDashboardBase}/?meeting=${mId}&lang=${lang}`, '_blank');
|
||||
} else {
|
||||
window.open(`${learningDashboardBase}/?meeting=${mId}&sessionToken=${sToken}&lang=${lang}`, '_blank');
|
||||
}
|
||||
};
|
||||
|
||||
export const setLearningDashboardCookie = (accessToken: string, mId: string) => {
|
||||
if (accessToken !== null) {
|
||||
const lifetime = new Date();
|
||||
lifetime.setTime(lifetime.getTime() + (3600000)); // 1h (extends 7d when open Dashboard)
|
||||
document.cookie = `ld-${mId}=${accessToken}; expires=${lifetime.toUTCString()}; path=/`;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export default {
|
||||
JoinErrorCodeTable,
|
||||
MeetingEndedTable,
|
||||
setLearningDashboardCookie,
|
||||
openLearningDashboardUrl,
|
||||
};
|
@ -0,0 +1,114 @@
|
||||
import { smallOnly } from '/imports/ui/stylesheets/styled-components/breakpoints';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import {
|
||||
borderRadius,
|
||||
lgPaddingX,
|
||||
} from '/imports/ui/stylesheets/styled-components/general';
|
||||
import {
|
||||
fontSizeSmall,
|
||||
fontSizeBase,
|
||||
fontSizeLarge,
|
||||
headingsFontWeight,
|
||||
lineHeightComputed,
|
||||
} from '/imports/ui/stylesheets/styled-components/typography';
|
||||
import {
|
||||
colorWhite,
|
||||
colorText,
|
||||
colorBackground,
|
||||
} from '/imports/ui/stylesheets/styled-components/palette';
|
||||
|
||||
const Parent = styled.div`
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: ${colorBackground};
|
||||
`;
|
||||
|
||||
const Modal = styled.div`
|
||||
display: flex;
|
||||
padding: ${lgPaddingX};
|
||||
background-color: ${colorWhite};
|
||||
flex-direction: column;
|
||||
border-radius: ${borderRadius};
|
||||
max-width: 95vw;
|
||||
width: 600px;
|
||||
`;
|
||||
|
||||
const Content = styled.div`
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
const Title = styled.h1`
|
||||
margin: 0;
|
||||
font-size: ${fontSizeLarge};
|
||||
font-weight: ${headingsFontWeight};
|
||||
`;
|
||||
|
||||
const Text = styled.div`
|
||||
color: ${colorText};
|
||||
font-weight: normal;
|
||||
padding: ${lineHeightComputed} 0;
|
||||
|
||||
@media ${smallOnly} {
|
||||
font-size: ${fontSizeSmall};
|
||||
}
|
||||
`;
|
||||
|
||||
const Wrapper = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const MeetingEndedButton = styled.button`
|
||||
border: none;
|
||||
overflow: visible;
|
||||
border-radius: 2px;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
height: 3rem;
|
||||
width: 3rem;
|
||||
display: flex !important;
|
||||
align-items: center;
|
||||
box-align: center;
|
||||
flex-align: center;
|
||||
box-pack: center;
|
||||
justify-content: center;
|
||||
flex-pack: center;
|
||||
color: var(--btn-primary-color, var(--color-white, #FFF));
|
||||
background-color: var(--btn-primary-bg, var(--color-primary, #0F70D7));
|
||||
border: 3px solid transparent;
|
||||
padding: calc(1.25rem / 2);
|
||||
@media ${smallOnly} {
|
||||
font-size: ${fontSizeBase};
|
||||
}
|
||||
`;
|
||||
|
||||
const TextArea = styled.textarea`
|
||||
resize: none;
|
||||
margin: 1rem auto;
|
||||
width: 100%;
|
||||
|
||||
&::placeholder {
|
||||
text-align: center;
|
||||
}
|
||||
`;
|
||||
|
||||
export default {
|
||||
Parent,
|
||||
Modal,
|
||||
Content,
|
||||
Title,
|
||||
Text,
|
||||
MeetingEndedButton,
|
||||
TextArea,
|
||||
Wrapper,
|
||||
};
|
@ -3,7 +3,7 @@ import Auth from '/imports/ui/services/auth';
|
||||
|
||||
|
||||
export default function allowRedirectToLogoutURL() {
|
||||
const ALLOW_DEFAULT_LOGOUT_URL = Meteor.settings.public.app.allowDefaultLogoutUrl;
|
||||
const ALLOW_DEFAULT_LOGOUT_URL = window.meetingClientSettings.public.app.allowDefaultLogoutUrl;
|
||||
const protocolPattern = /^((http|https):\/\/)/;
|
||||
if (Auth.logoutURL) {
|
||||
// default logoutURL
|
||||
|
@ -7,7 +7,7 @@ import PropTypes from 'prop-types';
|
||||
import Styled from './styles';
|
||||
import Meetings from '/imports/api/meetings';
|
||||
|
||||
const BBB_TABLET_APP_CONFIG = Meteor.settings.public.app.bbbTabletApp;
|
||||
const BBB_TABLET_APP_CONFIG = window.meetingClientSettings.public.app.bbbTabletApp;
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
title: {
|
||||
|
@ -7,7 +7,7 @@ import { defineMessages, injectIntl } from 'react-intl';
|
||||
import { notify } from '/imports/ui/services/notification';
|
||||
import TooltipContainer from '/imports/ui/components/common/tooltip/container';
|
||||
|
||||
const MUTE_ALERT_CONFIG = Meteor.settings.public.app.mutedAlert;
|
||||
const MUTE_ALERT_CONFIG = window.meetingClientSettings.public.app.mutedAlert;
|
||||
|
||||
const propTypes = {
|
||||
inputStream: PropTypes.objectOf(PropTypes.any).isRequired,
|
||||
|
@ -13,7 +13,7 @@ import { PANELS } from '/imports/ui/components/layout/enums';
|
||||
import useCurrentUser from '/imports/ui/core/hooks/useCurrentUser';
|
||||
import useChat from '/imports/ui/core/hooks/useChat';
|
||||
|
||||
const PUBLIC_CONFIG = Meteor.settings.public;
|
||||
const PUBLIC_CONFIG = window.meetingClientSettings.public;
|
||||
|
||||
const NavBarContainer = ({ children, ...props }) => {
|
||||
const { pluginsExtensibleAreasAggregatedState } = useContext(PluginsContext);
|
||||
|
@ -21,7 +21,7 @@ interface TalkingIndicatorSubscriptionData {
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore - temporary, while meteor exists in the project
|
||||
const APP_CONFIG = Meteor.settings.public.app;
|
||||
const APP_CONFIG = window.meetingClientSettings.public.app;
|
||||
const { enableTalkingIndicator } = APP_CONFIG;
|
||||
|
||||
const TALKING_INDICATORS_MAX = 8;
|
||||
|
@ -130,8 +130,8 @@ const defaultProps = {
|
||||
audioCaptionsEnabled: false,
|
||||
};
|
||||
|
||||
const ALLOW_FULLSCREEN = Meteor.settings.public.app.allowFullscreen;
|
||||
const BBB_TABLET_APP_CONFIG = Meteor.settings.public.app.bbbTabletApp;
|
||||
const ALLOW_FULLSCREEN = window.meetingClientSettings.public.app.allowFullscreen;
|
||||
const BBB_TABLET_APP_CONFIG = window.meetingClientSettings.public.app.bbbTabletApp;
|
||||
const { isSafari, isTabletApp } = browserInfo;
|
||||
const FULLSCREEN_CHANGE_EVENT = isSafari ? 'webkitfullscreenchange' : 'fullscreenchange';
|
||||
|
||||
@ -252,7 +252,7 @@ class OptionsDropdown extends PureComponent {
|
||||
showHelpButton: helpButton,
|
||||
helpLink,
|
||||
allowLogout: allowLogoutSetting,
|
||||
} = Meteor.settings.public.app;
|
||||
} = window.meetingClientSettings.public.app;
|
||||
|
||||
this.menuItems = [];
|
||||
|
||||
|
@ -11,9 +11,9 @@ import Header from '/imports/ui/components/common/control-header/component';
|
||||
import NotesDropdown from '/imports/ui/components/notes/notes-dropdown/container';
|
||||
import { isPresentationEnabled } from '../../services/features';
|
||||
|
||||
const CHAT_CONFIG = Meteor.settings.public.chat;
|
||||
const PUBLIC_CHAT_ID = CHAT_CONFIG.public_group_id;
|
||||
const DELAY_UNMOUNT_SHARED_NOTES = Meteor.settings.public.app.delayForUnmountOfSharedNote;
|
||||
const CHAT_CONFIG = window.meetingClientSettings.public.chat;
|
||||
const PUBLIC_CHAT_ID = CHAT_CONFIG.public_id;
|
||||
const DELAY_UNMOUNT_SHARED_NOTES = window.meetingClientSettings.public.app.delayForUnmountOfSharedNote;
|
||||
const intlMessages = defineMessages({
|
||||
hide: {
|
||||
id: 'app.notes.hide',
|
||||
|
@ -7,7 +7,7 @@ import Service from './service';
|
||||
import { uniqueId } from '/imports/utils/string-utils';
|
||||
|
||||
const DEBOUNCE_TIMEOUT = 15000;
|
||||
const NOTES_CONFIG = Meteor.settings.public.notes;
|
||||
const NOTES_CONFIG = window.meetingClientSettings.public.notes;
|
||||
const NOTES_IS_PINNABLE = NOTES_CONFIG.pinnable;
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
|
@ -5,7 +5,7 @@ import NotesService from '/imports/ui/components/notes/service';
|
||||
import { UploadingPresentations } from '/imports/api/presentations';
|
||||
import { uniqueId } from '/imports/utils/string-utils';
|
||||
|
||||
const PADS_CONFIG = Meteor.settings.public.pads;
|
||||
const PADS_CONFIG = window.meetingClientSettings.public.pads;
|
||||
|
||||
async function convertAndUpload(presentations, setPresentation, removePresentation) {
|
||||
let filename = 'Shared_Notes';
|
||||
|
@ -6,8 +6,8 @@ import { Session } from 'meteor/session';
|
||||
import { ACTIONS, PANELS } from '/imports/ui/components/layout/enums';
|
||||
import { isSharedNotesEnabled } from '/imports/ui/services/features';
|
||||
|
||||
const NOTES_CONFIG = Meteor.settings.public.notes;
|
||||
const ROLE_MODERATOR = Meteor.settings.public.user.role_moderator;
|
||||
const NOTES_CONFIG = window.meetingClientSettings.public.notes;
|
||||
const ROLE_MODERATOR = window.meetingClientSettings.public.user.role_moderator;
|
||||
|
||||
const hasPermission = () => {
|
||||
const user = Users.findOne(
|
||||
|
@ -23,7 +23,7 @@ const STATUS_FAILED = 'failed';
|
||||
// failed to connect and waiting to try to reconnect
|
||||
const STATUS_WAITING = 'waiting';
|
||||
|
||||
const METEOR_SETTINGS_APP = Meteor.settings.public.app;
|
||||
const METEOR_SETTINGS_APP = window.meetingClientSettings.public.app;
|
||||
|
||||
const REMAINING_TIME_THRESHOLD = METEOR_SETTINGS_APP.remainingTimeThreshold;
|
||||
|
||||
|
@ -48,7 +48,7 @@ let timeRemaining = 0;
|
||||
let prevTimeRemaining = 0;
|
||||
let lastAlertTime = null;
|
||||
|
||||
const METEOR_SETTINGS_APP = Meteor.settings.public.app;
|
||||
const METEOR_SETTINGS_APP = window.meetingClientSettings.public.app;
|
||||
const REMAINING_TIME_ALERT_THRESHOLD_ARRAY = METEOR_SETTINGS_APP.remainingTimeAlertThresholdArray;
|
||||
|
||||
const timeRemainingDep = new Tracker.Dependency();
|
||||
|
@ -8,7 +8,7 @@ import {
|
||||
isScreenBroadcasting,
|
||||
} from '/imports/ui/components/screenshare/service';
|
||||
|
||||
const PADS_CONFIG = Meteor.settings.public.pads;
|
||||
const PADS_CONFIG = window.meetingClientSettings.public.pads;
|
||||
const THROTTLE_TIMEOUT = 2000;
|
||||
|
||||
const getLang = () => {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { PadsSessions } from '/imports/api/pads';
|
||||
|
||||
const COOKIE_CONFIG = Meteor.settings.public.pads.cookie;
|
||||
const COOKIE_CONFIG = window.meetingClientSettings.public.pads.cookie;
|
||||
const PATH = COOKIE_CONFIG.path;
|
||||
const SAME_SITE = COOKIE_CONFIG.sameSite;
|
||||
const SECURE = COOKIE_CONFIG.secure;
|
||||
|
@ -18,7 +18,7 @@ import PluginDomElementManipulationManager from './dom-element-manipulation/mana
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore - temporary, while meteor exists in the project
|
||||
const PLUGINS_CONFIG = Meteor.settings.public.plugins;
|
||||
const PLUGINS_CONFIG = window.meetingClientSettings.public.plugins;
|
||||
|
||||
const PluginsEngineManager = () => {
|
||||
// If there is no plugin to load, the engine simply returns null
|
||||
|
@ -214,7 +214,7 @@ const intlMessages = defineMessages({
|
||||
}
|
||||
});
|
||||
|
||||
const POLL_SETTINGS = Meteor.settings.public.poll;
|
||||
const POLL_SETTINGS = window.meetingClientSettings.public.poll;
|
||||
|
||||
const ALLOW_CUSTOM_INPUT = POLL_SETTINGS.allowCustomResponseInput;
|
||||
const MAX_CUSTOM_FIELDS = POLL_SETTINGS.maxCustom;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user