Description
we have zoom sdk app and work fine with react & nestjs app .
but last week we figure an issue for joining webinar
type: 'JOIN_MEETING_FAILED', reason: 'Fail to join the meeting.', errorCode: 200
so i check our sdk app and its draft ! then i create new sdk app
but new app come with Client ID and Client Secret
and old app have sdk key and sdk secret
so when i try to use client.join
function there is no JoinOption for client id in frontend
export interface JoinOptions {
/**
* @param apiKey The Web SDK API key. Required if sdkKey isn't provided
*/
apiKey?: string;
/**
* @param sdkKey The Web SDK SDK key. Required if apiKey isn't provided
*/
sdkKey?: string;
/**
* @param signature Generated signature; please see docs for more info
*/
signature: string;
/**
* @param meetingNumber The Zoom Meeting number
*/
meetingNumber: string;
/**
* @param password The Zoom Meeting password
*/
password?: string;
/**
* @param userName The user's name
*/
userName: string;
/**
* @param userEmail The user's email
*/
userEmail?: string;
/**
* @param customerKey
*/
customerKey?: string;
/**
* @param tk Optional 'tk' param to join a webinar with registration
*/
tk?: string;
/**
* @param zak Optional 'zak' param to suppport oath start meeting/webinar
*/
zak?: string;
/**
* @param success join success callback
*/
success?: Function;
/**
* @param error join error callback
*/
error?: Function;
}
and i don’t know if this issue because last month all work fine in production
Browser Console Error
type: 'JOIN_MEETING_FAILED', reason: 'Fail to join the meeting.', errorCode: 200
Which Web Meeting SDK version?
2.6.0
Meeting SDK Code Snippets
The code snippets that are causing the error / issue so we can reproduce.
Screenshots
const joinZoomCredentials = {
apiKey: 'RHtKdsddsddiVZhTNKB1GmW6L9ZGw',
signature,
password,
meetingNumber: webinarId,
};
if (isStudent) {
joinZoomCredentials.tk = enrollment?.token;
joinZoomCredentials.userEmail = enrollment?.student.email;
joinZoomCredentials.userName = enrollment?.student.name;
} else {
joinZoomCredentials.zak = hostToken;
joinZoomCredentials.userEmail = hostEmail;
joinZoomCredentials.userName = currentUser.fullName;
}
client.on('connection-change', (payload) => {
if (payload.state === 'Closed') {
navigate(`/${PATHS.courses}/${courseId}`);
}
});
await client.join(joinZoomCredentials);
};
containerRef?.current && setUpZoomSDK();
containerRef?.current && startMeeting();
Device (please complete the following information):
- Device: Macbook Pro
- Browser: Chrome
Additional context
full code
import _ from 'lodash';
import ZoomMtgEmbedded from '@zoomus/websdk/embedded';
import { useNavigate, useParams } from 'react-router-dom';
import {
useCallback,
useEffect,
useRef,
} from 'react';
import {
Box,
Stack,
Typography,
useMediaQuery,
} from '@mui/material';
import { PATHS } from 'routes';
import { NoMatch } from 'routes/routes';
import { Page } from 'components/render';
import { useAuth, useLocale } from 'util/hooks';
import { useSessionDetails } from 'reactQuery/queries';
import { DismissibleAlert } from 'components/organisms';
import { mobileLandscape, zoomAudioIcon } from 'assets/images';
const {
REACT_APP_ZOOM_SDK_KEY,
} = process.env;
const client = ZoomMtgEmbedded.createClient();
const InstructionIcon = (props) => {
const { icon, alt = '' } = props;
return (
<img
src={icon}
alt={alt}
style={{
display: 'inline-block',
marginInline: '10px',
verticalAlign: 'middle',
}}
width="25"
height="25"
/>
);
};
const InstructionItem = (props) => {
const {
text = '',
icon = null,
alt = '',
} = props;
return (
<>
<Typography variant="bodyStandardRegular" lineHeight={2}>
{text}
</Typography>
{icon && <InstructionIcon icon={icon} alt={alt} />}
</>
);
};
const ViewSession = (props) => {
const { titleKey } = props;
const containerRef = useRef();
const navigate = useNavigate();
const { t, language } = useLocale();
const isLandscape = useMediaQuery('(max-height: 400px)');
const isMobileView = useMediaQuery(({ breakpoints }) => breakpoints.down('sm'));
const customMobileSx = isMobileView ? {
'#suspension-view-tabpanel-speaker > [class*="zmwebsdk-makeStyles-fullView"]': {
height: 'auto',
display: 'grid',
gridTemplateRows: 'minmax(200px, 300px)',
},
'& #suspension-view-tabpanel-speaker > [class*="zmwebsdk-makeStyles-fullView"] > div:nth-child(2)': {
display: 'none',
},
'#suspension-view-tabpanel-speaker > div:nth-child(1)': {
minHeight: '30vh',
},
} : {};
const customLandscapeSx = isLandscape ? {
height: 1,
} : {};
const { courseId, sessionId } = useParams();
const {
isStudent,
currentUser,
} = useAuth();
const {
data: {
enrollment = {},
...session
} = {},
isFetched = false,
} = useSessionDetails({ courseId, sessionId });
const bootStrapZoom = useCallback(async () => {
const setUpZoomSDK = () => {
const meetingSDKElement = containerRef.current;
client.init({
zoomAppRoot: meetingSDKElement,
language,
debug: false,
customize: {
video: {
popper: {
disableDraggable: true,
},
isResizable: false,
},
chat: {
popper: {
disableDraggable: false,
},
},
},
});
};
const startMeeting = async () => {
const {
signature,
webinarId,
password,
hostToken,
hostEmail,
} = session;
const joinZoomCredentials = {
sdkKey: REACT_APP_ZOOM_SDK_KEY,
signature,
password,
meetingNumber: webinarId,
};
if (isStudent) {
joinZoomCredentials.tk = enrollment?.token;
joinZoomCredentials.userEmail = enrollment?.student.email;
joinZoomCredentials.userName = enrollment?.student.name;
} else {
joinZoomCredentials.zak = hostToken;
joinZoomCredentials.userEmail = hostEmail;
joinZoomCredentials.userName = currentUser.fullName;
}
client.on('connection-change', (payload) => {
if (payload.state === 'Closed') {
navigate(`/${PATHS.courses}/${courseId}`);
}
});
await client.join(joinZoomCredentials);
};
containerRef?.current && setUpZoomSDK();
containerRef?.current && startMeeting();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [sessionId, containerRef?.current]);
useEffect(() => {
bootStrapZoom();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [sessionId, containerRef?.current]);
if (isFetched && _.isEmpty(session)) {
return <NoMatch />;
}
const instructions = [
{
key: 'enableAudio',
text: t('sessions.instructions.enableAudio'),
icon: zoomAudioIcon,
alt: 'audio icon',
show: true,
},
{
key: 'onMobile',
text: t('sessions.instructions.onMobile'),
icon: mobileLandscape,
alt: 'mobile icon',
show: isMobileView,
},
{
key: 'refreshPage',
text: t('sessions.instructions.refreshPage'),
icon: null,
alt: null,
show: true,
},
];
return (
<Page
titleKey={titleKey}
title={session?.title}
>
<Box sx={{
width: 0.9,
maxWidth: 'lg',
mx: 'auto',
height: 1,
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: {
xs: 'center',
md: 'start',
},
}}
>
<Stack width={1}>
<DismissibleAlert alertProps={{ sx: { py: 4, px: 5 } }}>
<Typography variant="bodyStandardMedium">
{t('sessions.instructions.header')}
:
</Typography>
<ul>
{instructions.map((instructionItem) => (
<li key={instructionItem.key}>
<InstructionItem {...instructionItem} />
</li>
))}
</ul>
</DismissibleAlert>
<Typography variant="h5" sx={{ color: 'common.white', my: 4 }}>
{session?.title}
</Typography>
</Stack>
<Box
ref={containerRef}
className="zoom-container"
sx={{
mt: 5,
width: 1,
position: 'relative',
height: {
xs: 0.6,
md: 0.7,
},
'& div:nth-child(1)': {
width: 1,
},
'& > div:nth-child(1)': {
minHeight: 200,
},
'& #suspension-view-tabpanel-speaker > div:nth-child(1)': {
justifyContent: 'center',
minHeight: '50vh',
},
'& button[title="Meeting Information"]': {
visibility: 'hidden',
},
...customMobileSx,
...customLandscapeSx,
}}
/>
</Box>
</Page>
);
};
export default ViewSession;