SEBSERV-148 testing fixing

This commit is contained in:
anhefti 2021-04-08 10:36:32 +02:00
parent d014dfe45a
commit 7fae3f4baf
10 changed files with 530 additions and 170 deletions

View file

@ -74,6 +74,9 @@ public final class Constants {
public static final String URL_ADDRESS_SEPARATOR = COLON.toString() + SLASH.toString() + SLASH.toString();
public static final String URL_PATH_SEPARATOR = SLASH.toString();
public static final String DYN_HTML_ATTR_OPEN = "%%_";
public static final String DYN_HTML_ATTR_CLOSE = "_%%";
public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";
public static final String TIME_ZONE_OFFSET_TAIL_FORMAT = "|ZZ";

View file

@ -11,12 +11,16 @@ package ch.ethz.seb.sebserver.gui.service.session.proctoring;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.io.IOUtils;
import org.apache.commons.text.StringSubstitutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings.ProctoringServerType;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService.ProctoringWindowData;
@ -28,58 +32,21 @@ public class JitsiWindowScriptResolver implements ProctoringWindowScriptResolver
private static final Logger log = LoggerFactory.getLogger(JitsiWindowScriptResolver.class);
private static final String ATTR_SUBJECT = "_subject_";
private static final String ATTR_ACCESS_TOKEN = "_accessToken_";
private static final String ATTR_ROOM_NAME = "_roomName_";
private static final String ATTR_HOST = "_host_";
static final String RES_PATH =
"classpath:ch/ethz/seb/sebserver/gui/service/session/proctoring/jitsiWindow.html";
// @formatter:off
private static final String JITSI_WINDOW_HTML =
"<!DOCTYPE html>" +
"<html>" +
"<head>" +
" <title></title>" +
" <script src='https://%%_" + ATTR_HOST + "_%%/external_api.js'></script>" +
"</head>" +
"" +
"<body>" +
"<div id=\"proctoring\"></div> " +
"</body>" +
"<script>" +
" const options = {\n" +
" parentNode: document.querySelector('#proctoring'),\n" +
" roomName: '%%_" + ATTR_ROOM_NAME + "_%%',\n" +
// " width: window.innerWidth,\n" +
" height: window.innerHeight - 4,\n" +
" jwt: '%%_" + ATTR_ACCESS_TOKEN + "_%%',\n" +
" configOverwrite: { startAudioOnly: false, startWithAudioMuted: true, startWithVideoMuted: false, disable1On1Mode: true },\n" +
" interfaceConfigOverwrite: { " +
"TOOLBAR_BUTTONS: [\r\n" +
" 'microphone', 'camera',\r\n" +
" 'fodeviceselection', 'profile', 'chat', 'recording',\r\n" +
" 'livestreaming', 'settings',\r\n" +
" 'videoquality', 'filmstrip', 'feedback',\r\n" +
" 'tileview', 'help', 'mute-everyone', 'security'\r\n" +
" ],"
+ "SHOW_WATERMARK_FOR_GUESTS: false, "
+ "RECENT_LIST_ENABLED: false, "
+ "HIDE_INVITE_MORE_HEADER: true, "
+ "DISABLE_RINGING: true, "
+ "DISABLE_PRESENCE_STATUS: true, "
+ "DISABLE_JOIN_LEAVE_NOTIFICATIONS: true, "
+ "GENERATE_ROOMNAMES_ON_WELCOME_PAGE: false, "
+ "MOBILE_APP_PROMO: false, "
+ "SHOW_JITSI_WATERMARK: false, "
+ "DISABLE_PRESENCE_STATUS: true, "
+ "DISABLE_RINGING: true, "
+ "DISABLE_VIDEO_BACKGROUND: false, "
+ "filmStripOnly: false }\n" +
" }\n" +
" const meetAPI = new JitsiMeetExternalAPI(\"%%_" + ATTR_HOST + "_%%\", options);\n" +
" meetAPI.executeCommand('subject', '%%_" + ATTR_SUBJECT + "_%%');\n" +
"</script>" +
"</html>";
// @formatter:on
private static final String ATTR_SUBJECT = "ATTR_SUBJECT";
private static final String ATTR_ACCESS_TOKEN = "ATTR_ACCESS_TOKEN";
private static final String ATTR_ROOM_NAME = "ATTR_ROOM_NAME";
private static final String ATTR_HOST = "ATTR_HOST";
private final Resource resourceFile;
public JitsiWindowScriptResolver(
@Value(RES_PATH) final Resource resourceFile) {
this.resourceFile = resourceFile;
}
@Override
public boolean applies(final ProctoringWindowData data) {
@ -93,14 +60,28 @@ public class JitsiWindowScriptResolver implements ProctoringWindowScriptResolver
@Override
public String getProctoringWindowScript(final ProctoringWindowData data) {
final Map<String, String> args = new HashMap<>();
args.put(ATTR_HOST, data.connectionData.serverHost);
args.put(ATTR_ROOM_NAME, data.connectionData.roomName);
args.put(ATTR_ACCESS_TOKEN, String.valueOf(data.connectionData.accessToken));
args.put(ATTR_SUBJECT, data.connectionData.subject);
return new StringSubstitutor(args, "%%_", "_%%")
.replace(JITSI_WINDOW_HTML);
return new StringSubstitutor(
args,
Constants.DYN_HTML_ATTR_OPEN,
Constants.DYN_HTML_ATTR_CLOSE)
.replace(getHTMLWindow());
}
private String getHTMLWindow() {
try {
return IOUtils.toString(this.resourceFile.getInputStream());
} catch (final Exception e) {
log.error("Failed to load Jitsi Meet window resources", e);
return "ERROR: " + e.getLocalizedMessage();
}
}
}

View file

@ -10,10 +10,24 @@ package ch.ethz.seb.sebserver.gui.service.session.proctoring;
import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService.ProctoringWindowData;
/** Defines a proctoring window script resolver that generates the
* proctoring window html and script code for a particular
* proctoring service. */
public interface ProctoringWindowScriptResolver {
/** Indicates if the concrete implementation applies to given proctoring data.
* Usually this looks after the proctoring service type within the given data
* and returns true if the implementation is compatible with the given proctoring
* service type.
*
* @param data ProctoringWindowData instance containing actual proctoring data
* @return true if a concrete implementation applies to the given data */
boolean applies(ProctoringWindowData data);
/** Produces the html and java script page to open in a proctoring window pop-up.
*
* @param data ProctoringWindowData instance containing actual proctoring data
* @return the html and java script page to open in a proctoring window pop-up */
String getProctoringWindowScript(ProctoringWindowData data);
}

View file

@ -11,13 +11,17 @@ package ch.ethz.seb.sebserver.gui.service.session.proctoring;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.text.StringSubstitutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings.ProctoringServerType;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService.ProctoringWindowData;
@ -29,120 +33,24 @@ public class ZoomWindowScriptResolver implements ProctoringWindowScriptResolver
private static final Logger log = LoggerFactory.getLogger(ZoomWindowScriptResolver.class);
private static final String ATTR_SUBJECT = "_subject_";
private static final String ATTR_API_KEY = "_apiKey_";
private static final String ATTR_ACCESS_TOKEN = "_accessToken_";
private static final String ATTR_ROOM_KEY = "_roomKey_";
private static final String ATTR_ROOM_NAME = "_roomName_";
private static final String ATTR_HOST = "_host_";
private static final String ATTR_USER_NAME = "_username_";
static final String RES_PATH =
"classpath:ch/ethz/seb/sebserver/gui/service/session/proctoring/zoomWindow.html";
// @formatter:off
private static final String ZOOM_WINDOW_HTML =
"<html>\n"
+ " <head>\n"
+ " <meta charset=\"utf-8\" />\n"
+ " <link type=\"text/css\" rel=\"stylesheet\" href=\"https://source.zoom.us/1.9.0/css/bootstrap.css\" />\n"
+ " <link type=\"text/css\" rel=\"stylesheet\" href=\"https://source.zoom.us/1.9.0/css/react-select.css\" />\n"
+ " </head>\n"
+ " <body>\n"
+ " <script src=\"https://source.zoom.us/1.9.0/lib/vendor/react.min.js\"></script>\n"
+ " <script src=\"https://source.zoom.us/1.9.0/lib/vendor/react-dom.min.js\"></script>\n"
+ " <script src=\"https://source.zoom.us/1.9.0/lib/vendor/redux.min.js\"></script>\n"
+ " <script src=\"https://source.zoom.us/1.9.0/lib/vendor/redux-thunk.min.js\"></script>\n"
+ " <script src=\"https://source.zoom.us/1.9.0/lib/vendor/jquery.min.js\"></script>\n"
+ " <script src=\"https://source.zoom.us/1.9.0/lib/vendor/lodash.min.js\"></script>\n"
+ " <script src=\"https://source.zoom.us/zoom-meeting-1.9.0.min.js\"></script>\n"
+ " <script src=\"https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9/crypto-js.min.js\"></script>\n"
+ " <script type=\"text/javascript\">\n"
+ "\n"
+ " console.log(\"Checking system requirements...\");\n"
+ " console.log(JSON.stringify(ZoomMtg.checkSystemRequirements()));\n"
+ "\n"
+ " console.log(\"Initializing Zoom...\");\n"
+ " ZoomMtg.setZoomJSLib('https://source.zoom.us/1.9.0/lib', '/av');\n"
+ " ZoomMtg.preLoadWasm();\n"
+ " ZoomMtg.prepareJssdk();\n"
+ "\n"
+ " const API_KEY = \"%%_" + ATTR_API_KEY + "_%%\";\n"
+ " const config = {\n"
+ " meetingNumber: %%_" + ATTR_ROOM_NAME + "_%%,\n"
+ " leaveUrl: '%%_" + ATTR_HOST + "_%%',\n"
+ " userName: '%%_" + ATTR_USER_NAME + "_%%',\n"
+ " passWord: '%%_" + ATTR_ROOM_KEY + "_%%',\n"
+ " role: 1 // 1 for host; 0 for attendee\n"
+ " };\n"
+ "\n"
+ " const signature = '%%_" + ATTR_ACCESS_TOKEN + "_%%';\n"
+ "\n"
+ " console.log(\"Initializing meeting...\");\n"
+ "\n"
+ " // See documentation: https://zoom.github.io/sample-app-web/ZoomMtg.html#init\n"
+ " ZoomMtg.init({\n"
+ " debug: true, //optional\n"
+ " leaveUrl: config.leaveUrl, //required\n"
+ " // webEndpoint: 'PSO web domain', // PSO option\n"
+ " showMeetingHeader: true, //option\n"
+ " disableInvite: false, //optional\n"
+ " disableCallOut: false, //optional\n"
+ " disableRecord: false, //optional\n"
+ " disableJoinAudio: false, //optional\n"
+ " audioPanelAlwaysOpen: true, //optional\n"
+ " showPureSharingContent: false, //optional\n"
+ " isSupportAV: true, //optional,\n"
+ " isSupportChat: true, //optional,\n"
+ " isSupportQA: true, //optional,\n"
+ " isSupportCC: true, //optional,\n"
+ " screenShare: true, //optional,\n"
+ " rwcBackup: '', //optional,\n"
+ " videoDrag: true, //optional,\n"
+ " sharingMode: 'both', //optional,\n"
+ " videoHeader: true, //optional,\n"
+ " isLockBottom: true, // optional,\n"
+ " isSupportNonverbal: true, // optional,\n"
+ " isShowJoiningErrorDialog: true, // optional,\n"
+ " inviteUrlFormat: '', // optional\n"
+ " loginWindow: { // optional,\n"
+ " width: 400,\n"
+ " height: 380\n"
+ " },\n"
+ " meetingInfo: [ // optional\n"
+ " 'topic',\n"
+ " 'host',\n"
+ " 'mn',\n"
+ " 'pwd',\n"
+ " 'invite',\n"
+ " 'participant',\n"
+ " 'dc'\n"
+ " ],\n"
+ " disableVoIP: false, // optional\n"
+ " disableReport: false, // optional\n"
+ " error: function (res) {\n"
+ " console.warn(\"INIT ERROR\")\n"
+ " console.log(res)\n"
+ " },\n"
+ " success: function () {\n"
+ " console.log(\"INIT SUCCESS\")\n"
+ " ZoomMtg.join({\n"
+ " signature: signature,\n"
+ " apiKey: API_KEY,\n"
+ " meetingNumber: config.meetingNumber,\n"
+ " userName: config.userName,\n"
+ " passWord: config.passWord,\n"
+ " success(res) {\n"
+ " console.log(\"JOIN SUCCESS\")\n"
+ " },\n"
+ " error(res) {\n"
+ " console.warn(\"JOIN ERROR\")\n"
+ " console.log(res)\n"
+ " }\n"
+ " })\n"
+ " }\n"
+ " })\n"
+ " </script>\n"
+ " </body>\n"
+ "</html>";
// @formatter:on
private static final String ATTR_SUBJECT = "ATTR_SUBJECT";
private static final String ATTR_API_KEY = "ATTR_API_KEY";
private static final String ATTR_ACCESS_TOKEN = "ATTR_ACCESS_TOKEN";
private static final String ATTR_ROOM_KEY = "ATTR_ROOM_KEY";
private static final String ATTR_ROOM_NAME = "ATTR_ROOM_NAME";
private static final String ATTR_HOST = "ATTR_HOST";
private static final String ATTR_USER_NAME = "ATTR_USER_NAME";
private final Resource resourceFile;
public ZoomWindowScriptResolver(
@Value(RES_PATH) final Resource resourceFile) {
this.resourceFile = resourceFile;
}
@Override
public boolean applies(final ProctoringWindowData data) {
@ -169,8 +77,22 @@ public class ZoomWindowScriptResolver implements ProctoringWindowScriptResolver
args.put(ATTR_SUBJECT, data.connectionData.subject);
args.put(ATTR_USER_NAME, data.connectionData.userName);
return new StringSubstitutor(args, "%%_", "_%%")
.replace(ZOOM_WINDOW_HTML);
final String htmlWindow = getHTMLWindow();
final String replace = new StringSubstitutor(
args,
Constants.DYN_HTML_ATTR_OPEN,
Constants.DYN_HTML_ATTR_CLOSE)
.replace(htmlWindow);
return replace;
}
private String getHTMLWindow() {
try {
return IOUtils.toString(this.resourceFile.getInputStream());
} catch (final Exception e) {
log.error("Failed to load Jitsi Meet window resources", e);
return "ERROR: " + e.getLocalizedMessage();
}
}
}

View file

@ -0,0 +1,47 @@
<!DOCTYPE html>
<html>
<head>
<title></title>
<script src='https://%%_ATTR_HOST_%%/external_api.js'></script>
</head>
<body>
<div id='proctoring'></div>
</body>
<script>
const options = {
parentNode: document.querySelector('#proctoring'),
roomName: '%%_ATTR_ROOM_NAME_%%',
// width: window.innerWidth,
height: window.innerHeight - 4,
jwt: '%%_ATTR_ACCESS_TOKEN_%%',
configOverwrite: { startAudioOnly: false, startWithAudioMuted: true, startWithVideoMuted: false, disable1On1Mode: true },
interfaceConfigOverwrite: {
TOOLBAR_BUTTONS: [
'microphone', 'camera',
'fodeviceselection', 'profile', 'chat', 'recording',
'livestreaming', 'settings',
'videoquality', 'filmstrip', 'feedback',
'tileview', 'help', 'mute-everyone', 'security'
],
SHOW_WATERMARK_FOR_GUESTS: false,
RECENT_LIST_ENABLED: false,
HIDE_INVITE_MORE_HEADER: true,
DISABLE_RINGING: true,
DISABLE_PRESENCE_STATUS: true,
DISABLE_JOIN_LEAVE_NOTIFICATIONS: true,
GENERATE_ROOMNAMES_ON_WELCOME_PAGE: false,
MOBILE_APP_PROMO: false,
SHOW_JITSI_WATERMARK: false,
DISABLE_PRESENCE_STATUS: true,
DISABLE_RINGING: true,
DISABLE_VIDEO_BACKGROUND: false,
filmStripOnly: false
}
}
const meetAPI = new JitsiMeetExternalAPI('%%_ATTR_HOST_%%', options);
meetAPI.executeCommand('subject', '%%_ATTR_SUBJECT_%%');
</script>
</html>

View file

@ -0,0 +1,102 @@
<html>
<head>
<meta charset='utf-8' />
<link type='text/css' rel='stylesheet' href='https://source.zoom.us/1.9.0/css/bootstrap.css' />
<link type='text/css' rel='stylesheet' href='https://source.zoom.us/1.9.0/css/react-select.css' />
</head>
<body>
<script src='https://source.zoom.us/1.9.0/lib/vendor/react.min.js'></script>
<script src='https://source.zoom.us/1.9.0/lib/vendor/react-dom.min.js'></script>
<script src='https://source.zoom.us/1.9.0/lib/vendor/redux.min.js'></script>
<script src='https://source.zoom.us/1.9.0/lib/vendor/redux-thunk.min.js'></script>
<script src='https://source.zoom.us/1.9.0/lib/vendor/jquery.min.js'></script>
<script src='https://source.zoom.us/1.9.0/lib/vendor/lodash.min.js'></script>
<script src='https://source.zoom.us/zoom-meeting-1.9.0.min.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9/crypto-js.min.js'></script>
<script type='text/javascript'>
console.log('Checking system requirements...');
console.log(JSON.stringify(ZoomMtg.checkSystemRequirements()));
console.log('Initializing Zoom...');
ZoomMtg.setZoomJSLib('https://source.zoom.us/1.9.0/lib', '/av');
ZoomMtg.preLoadWasm();
ZoomMtg.prepareJssdk();
const API_KEY = '%%_ATTR_API_KEY_%%';
const config = {
meetingNumber: %%_ATTR_ROOM_NAME_%%,
leaveUrl: '%%_ATTR_HOST_%%',
userName: '%%_ATTR_USER_NAME_%%',
passWord: '%%_ATTR_ROOM_KEY_%%',
role: 1 // 1 for host; 0 for attendee
};
const signature = '%%_ATTR_ACCESS_TOKEN_%%';
console.log('Initializing meeting...');
// See documentation: https://zoom.github.io/sample-app-web/ZoomMtg.html#init
ZoomMtg.init({
debug: true, //optional
leaveUrl: config.leaveUrl, //required
// webEndpoint: 'PSO web domain', // PSO option
showMeetingHeader: true, //option
disableInvite: false, //optional
disableCallOut: false, //optional
disableRecord: false, //optional
disableJoinAudio: false, //optional
audioPanelAlwaysOpen: true, //optional
showPureSharingContent: false, //optional
isSupportAV: true, //optional,
isSupportChat: true, //optional,
isSupportQA: true, //optional,
isSupportCC: true, //optional,
screenShare: true, //optional,
rwcBackup: '', //optional,
videoDrag: true, //optional,
sharingMode: 'both', //optional,
videoHeader: true, //optional,
isLockBottom: true, // optional,
isSupportNonverbal: true, // optional,
isShowJoiningErrorDialog: true, // optional,
inviteUrlFormat: '', // optional
loginWindow: { // optional,
width: 400,
height: window.innerHeight - 4
},
meetingInfo: [ // optional
'topic',
'host',
'mn',
'pwd',
'invite',
'participant',
'dc'
],
disableVoIP: false, // optional
disableReport: false, // optional
error: function (res) {
console.warn('INIT ERROR')
console.log(res)
},
success: function () {
console.log('INIT SUCCESS')
ZoomMtg.join({
signature: signature,
apiKey: API_KEY,
meetingNumber: config.meetingNumber,
userName: config.userName,
passWord: config.passWord,
success(res) {
console.log('JOIN SUCCESS')
},
error(res) {
console.warn('JOIN ERROR')
console.log(res)
}
})
}
})
</script>
</body>
</html>

View file

@ -131,7 +131,7 @@ public interface ZoomRoomRequestResponse {
@JsonIgnoreProperties(ignoreUnknown = true)
static class Settings {
@JsonProperty final boolean host_video = false;
@JsonProperty final boolean host_video = true;
@JsonProperty final boolean participant_video = true;
@JsonProperty final boolean join_before_host = true;
@JsonProperty final int jbh_time = 0;

View file

@ -1403,7 +1403,7 @@ sebserver.examconfig.props.label.zoomFeatureFlagChat=Enable Chat
sebserver.examconfig.props.label.zoomFeatureFlagChat.tooltip=
sebserver.examconfig.props.label.zoomFeatureFlagCloseCaptions=Enable Close Captions
sebserver.examconfig.props.label.zoomFeatureFlagCloseCaptions.tooltip=
sebserver.examconfig.props.label.zoomFeatureFlagDisplayingName=Display Meeting Name
sebserver.examconfig.props.label.zoomFeatureFlagDisplayingMeetingName=Display Meeting Name
sebserver.examconfig.props.label.zoomFeatureFlagDisplayMeetingName.tooltip
sebserver.examconfig.props.label.zoomFeatureFlagRaiseHand=Enable Raise Hand
sebserver.examconfig.props.label.zoomFeatureFlagRaiseHand.tooltip=

View file

@ -0,0 +1,118 @@
/*
* Copyright (c) 2021 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package ch.ethz.seb.sebserver.gui.service.session.proctoring;
import static org.junit.Assert.*;
import org.junit.Test;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringRoomConnection;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings.ProctoringServerType;
import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService.ProctoringWindowData;
public class JitsiWindowScriptResolverTest {
@Test
public void testJitsiWindowScriptResolver() {
final DefaultResourceLoader defaultResourceLoader = new DefaultResourceLoader();
final Resource resource = defaultResourceLoader.getResource(JitsiWindowScriptResolver.RES_PATH);
final JitsiWindowScriptResolver jitsiWindowScriptResolver = new JitsiWindowScriptResolver(resource);
final ProctoringWindowData proctoringWindowData = new ProctoringWindowData(
"0",
"Test_Window",
new ProctoringRoomConnection(
ProctoringServerType.JITSI_MEET,
"CONNECTION_TOKEN",
"HOST",
"URL",
"ROOM",
"SUBJECT",
"ACCESS_TOKEN",
"API_KEY",
"ROOM_KEY",
"MEETING_ID",
"USER_NAME"));
final ProctoringWindowData proctoringWindowDataOther = new ProctoringWindowData(
"0",
"Test_Window",
new ProctoringRoomConnection(
ProctoringServerType.ZOOM,
"CONNECTION_TOKEN",
"HOST",
"URL",
"ROOM",
"SUBJECT",
"ACCESS_TOKEN",
"API_KEY",
"ROOM_KEY",
"MEETING_ID",
"USER_NAME"));
assertFalse(jitsiWindowScriptResolver.applies(proctoringWindowDataOther));
assertTrue(jitsiWindowScriptResolver.applies(proctoringWindowData));
final String proctoringWindowScript = jitsiWindowScriptResolver
.getProctoringWindowScript(proctoringWindowData);
assertEquals(
"<!DOCTYPE html>\r\n"
+ "<html>\r\n"
+ " <head>\r\n"
+ " <title></title>\r\n"
+ " <script src='https://HOST/external_api.js'></script>\r\n"
+ "</head>\r\n"
+ "<body>\r\n"
+ "<div id='proctoring'></div>\r\n"
+ "</body>\r\n"
+ "<script>\r\n"
+ "\r\n"
+ " const options = {\r\n"
+ " parentNode: document.querySelector('#proctoring'),\r\n"
+ " roomName: 'ROOM',\r\n"
+ " // width: window.innerWidth,\r\n"
+ " height: window.innerHeight - 4,\r\n"
+ " jwt: 'ACCESS_TOKEN',\r\n"
+ " configOverwrite: { startAudioOnly: false, startWithAudioMuted: true, startWithVideoMuted: false, disable1On1Mode: true },\r\n"
+ " interfaceConfigOverwrite: { \r\n"
+ " TOOLBAR_BUTTONS: [\r\n"
+ " 'microphone', 'camera',\r\n"
+ " 'fodeviceselection', 'profile', 'chat', 'recording',\r\n"
+ " 'livestreaming', 'settings',\r\n"
+ " 'videoquality', 'filmstrip', 'feedback',\r\n"
+ " 'tileview', 'help', 'mute-everyone', 'security'\r\n"
+ " ],\r\n"
+ " SHOW_WATERMARK_FOR_GUESTS: false,\r\n"
+ " RECENT_LIST_ENABLED: false,\r\n"
+ " HIDE_INVITE_MORE_HEADER: true,\r\n"
+ " DISABLE_RINGING: true,\r\n"
+ " DISABLE_PRESENCE_STATUS: true,\r\n"
+ " DISABLE_JOIN_LEAVE_NOTIFICATIONS: true,\r\n"
+ " GENERATE_ROOMNAMES_ON_WELCOME_PAGE: false,\r\n"
+ " MOBILE_APP_PROMO: false,\r\n"
+ " SHOW_JITSI_WATERMARK: false,\r\n"
+ " DISABLE_PRESENCE_STATUS: true,\r\n"
+ " DISABLE_RINGING: true,\r\n"
+ " DISABLE_VIDEO_BACKGROUND: false,\r\n"
+ " filmStripOnly: false\r\n"
+ " }\r\n"
+ " }\r\n"
+ " \r\n"
+ " const meetAPI = new JitsiMeetExternalAPI('HOST', options);\r\n"
+ " meetAPI.executeCommand('subject', 'SUBJECT');\r\n"
+ " \r\n"
+ "</script>\r\n"
+ "</html>",
proctoringWindowScript);
}
}

View file

@ -0,0 +1,173 @@
/*
* Copyright (c) 2021 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package ch.ethz.seb.sebserver.gui.service.session.proctoring;
import static org.junit.Assert.*;
import org.junit.Test;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringRoomConnection;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings.ProctoringServerType;
import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService.ProctoringWindowData;
public class ZoomWindowScriptResolverTest {
@Test
public void testJitsiWindowScriptResolver() {
final DefaultResourceLoader defaultResourceLoader = new DefaultResourceLoader();
final Resource resource = defaultResourceLoader.getResource(ZoomWindowScriptResolver.RES_PATH);
final ZoomWindowScriptResolver zoomWindowScriptResolver = new ZoomWindowScriptResolver(resource);
final ProctoringWindowData proctoringWindowDataZoom = new ProctoringWindowData(
"0",
"Test_Window",
new ProctoringRoomConnection(
ProctoringServerType.ZOOM,
"CONNECTION_TOKEN",
"HOST",
"URL",
"ROOM",
"SUBJECT",
"ACCESS_TOKEN",
"API_KEY",
"ROOM_KEY",
"MEETING_ID",
"USER_NAME"));
final ProctoringWindowData proctoringWindowDataOther = new ProctoringWindowData(
"0",
"Test_Window",
new ProctoringRoomConnection(
ProctoringServerType.JITSI_MEET,
"CONNECTION_TOKEN",
"HOST",
"URL",
"ROOM",
"SUBJECT",
"ACCESS_TOKEN",
"API_KEY",
"ROOM_KEY",
"MEETING_ID",
"USER_NAME"));
assertFalse(zoomWindowScriptResolver.applies(proctoringWindowDataOther));
assertTrue(zoomWindowScriptResolver.applies(proctoringWindowDataZoom));
final String proctoringWindowScript = zoomWindowScriptResolver
.getProctoringWindowScript(proctoringWindowDataZoom);
assertEquals(
"<html>\r\n"
+ " <head>\r\n"
+ " <meta charset='utf-8' />\r\n"
+ " <link type='text/css' rel='stylesheet' href='https://source.zoom.us/1.9.0/css/bootstrap.css' />\r\n"
+ " <link type='text/css' rel='stylesheet' href='https://source.zoom.us/1.9.0/css/react-select.css' />\r\n"
+ " </head>\r\n"
+ " <body>\r\n"
+ " <script src='https://source.zoom.us/1.9.0/lib/vendor/react.min.js'></script>\r\n"
+ " <script src='https://source.zoom.us/1.9.0/lib/vendor/react-dom.min.js'></script>\r\n"
+ " <script src='https://source.zoom.us/1.9.0/lib/vendor/redux.min.js'></script>\r\n"
+ " <script src='https://source.zoom.us/1.9.0/lib/vendor/redux-thunk.min.js'></script>\r\n"
+ " <script src='https://source.zoom.us/1.9.0/lib/vendor/jquery.min.js'></script>\r\n"
+ " <script src='https://source.zoom.us/1.9.0/lib/vendor/lodash.min.js'></script>\r\n"
+ " <script src='https://source.zoom.us/zoom-meeting-1.9.0.min.js'></script>\r\n"
+ " <script src='https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9/crypto-js.min.js'></script>\r\n"
+ " <script type='text/javascript'>\r\n"
+ "\r\n"
+ " console.log('Checking system requirements...');\r\n"
+ " console.log(JSON.stringify(ZoomMtg.checkSystemRequirements()));\r\n"
+ "\r\n"
+ " console.log('Initializing Zoom...');\r\n"
+ " ZoomMtg.setZoomJSLib('https://source.zoom.us/1.9.0/lib', '/av');\r\n"
+ " ZoomMtg.preLoadWasm();\r\n"
+ " ZoomMtg.prepareJssdk();\r\n"
+ "\r\n"
+ " const API_KEY = 'ROOM_KEY';\r\n"
+ " const config = {\r\n"
+ " meetingNumber: MEETING_ID,\r\n"
+ " leaveUrl: 'HOST',\r\n"
+ " userName: 'USER_NAME',\r\n"
+ " passWord: 'API_KEY',\r\n"
+ " role: 1 // 1 for host; 0 for attendee\r\n"
+ " };\r\n"
+ "\r\n"
+ " const signature = 'ACCESS_TOKEN';\r\n"
+ " console.log('Initializing meeting...');\r\n"
+ "\r\n"
+ " // See documentation: https://zoom.github.io/sample-app-web/ZoomMtg.html#init\r\n"
+ " ZoomMtg.init({\r\n"
+ " debug: true, //optional\r\n"
+ " leaveUrl: config.leaveUrl, //required\r\n"
+ " // webEndpoint: 'PSO web domain', // PSO option\r\n"
+ " showMeetingHeader: true, //option\r\n"
+ " disableInvite: false, //optional\r\n"
+ " disableCallOut: false, //optional\r\n"
+ " disableRecord: false, //optional\r\n"
+ " disableJoinAudio: false, //optional\r\n"
+ " audioPanelAlwaysOpen: true, //optional\r\n"
+ " showPureSharingContent: false, //optional\r\n"
+ " isSupportAV: true, //optional,\r\n"
+ " isSupportChat: true, //optional,\r\n"
+ " isSupportQA: true, //optional,\r\n"
+ " isSupportCC: true, //optional,\r\n"
+ " screenShare: true, //optional,\r\n"
+ " rwcBackup: '', //optional,\r\n"
+ " videoDrag: true, //optional,\r\n"
+ " sharingMode: 'both', //optional,\r\n"
+ " videoHeader: true, //optional,\r\n"
+ " isLockBottom: true, // optional,\r\n"
+ " isSupportNonverbal: true, // optional,\r\n"
+ " isShowJoiningErrorDialog: true, // optional,\r\n"
+ " inviteUrlFormat: '', // optional\r\n"
+ " loginWindow: { // optional,\r\n"
+ " width: 400,\r\n"
+ " height: window.innerHeight - 4\r\n"
+ " },\r\n"
+ " meetingInfo: [ // optional\r\n"
+ " 'topic',\r\n"
+ " 'host',\r\n"
+ " 'mn',\r\n"
+ " 'pwd',\r\n"
+ " 'invite',\r\n"
+ " 'participant',\r\n"
+ " 'dc'\r\n"
+ " ],\r\n"
+ " disableVoIP: false, // optional\r\n"
+ " disableReport: false, // optional\r\n"
+ " error: function (res) {\r\n"
+ " console.warn('INIT ERROR')\r\n"
+ " console.log(res)\r\n"
+ " },\r\n"
+ " success: function () {\r\n"
+ " console.log('INIT SUCCESS')\r\n"
+ " ZoomMtg.join({\r\n"
+ " signature: signature,\r\n"
+ " apiKey: API_KEY,\r\n"
+ " meetingNumber: config.meetingNumber,\r\n"
+ " userName: config.userName,\r\n"
+ " passWord: config.passWord,\r\n"
+ " success(res) {\r\n"
+ " console.log('JOIN SUCCESS')\r\n"
+ " },\r\n"
+ " error(res) {\r\n"
+ " console.warn('JOIN ERROR')\r\n"
+ " console.log(res)\r\n"
+ " }\r\n"
+ " })\r\n"
+ " }\r\n"
+ " })\r\n"
+ " </script>\r\n"
+ " </body>\r\n"
+ "</html>",
proctoringWindowScript);
}
}