SEBWIN-475: Implemented basic Zoom proctoring integration.
This commit is contained in:
parent
7ad1d6ae5d
commit
da39fb1f59
8 changed files with 120 additions and 32 deletions
|
@ -92,6 +92,9 @@ namespace SafeExamBrowser.Configuration.ConfigurationData.DataMapping
|
||||||
case Keys.Proctoring.Zoom.Signature:
|
case Keys.Proctoring.Zoom.Signature:
|
||||||
MapZoomSignature(settings, value);
|
MapZoomSignature(settings, value);
|
||||||
break;
|
break;
|
||||||
|
case Keys.Proctoring.Zoom.Subject:
|
||||||
|
MapZoomSubject(settings, value);
|
||||||
|
break;
|
||||||
case Keys.Proctoring.Zoom.UserName:
|
case Keys.Proctoring.Zoom.UserName:
|
||||||
MapZoomUserName(settings, value);
|
MapZoomUserName(settings, value);
|
||||||
break;
|
break;
|
||||||
|
@ -317,6 +320,14 @@ namespace SafeExamBrowser.Configuration.ConfigurationData.DataMapping
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void MapZoomSubject(AppSettings settings, object value)
|
||||||
|
{
|
||||||
|
if (value is string subject)
|
||||||
|
{
|
||||||
|
settings.Proctoring.Zoom.Subject = subject;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void MapZoomUserName(AppSettings settings, object value)
|
private void MapZoomUserName(AppSettings settings, object value)
|
||||||
{
|
{
|
||||||
if (value is string name)
|
if (value is string name)
|
||||||
|
|
|
@ -233,7 +233,10 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
|
||||||
settings.Proctoring.JitsiMeet.VideoMuted = false;
|
settings.Proctoring.JitsiMeet.VideoMuted = false;
|
||||||
settings.Proctoring.ShowTaskbarNotification = true;
|
settings.Proctoring.ShowTaskbarNotification = true;
|
||||||
settings.Proctoring.WindowVisibility = WindowVisibility.Hidden;
|
settings.Proctoring.WindowVisibility = WindowVisibility.Hidden;
|
||||||
|
settings.Proctoring.Zoom.AllowChat = false;
|
||||||
settings.Proctoring.Zoom.Enabled = false;
|
settings.Proctoring.Zoom.Enabled = false;
|
||||||
|
settings.Proctoring.Zoom.ReceiveAudio = false;
|
||||||
|
settings.Proctoring.Zoom.ReceiveVideo = false;
|
||||||
|
|
||||||
settings.Security.AllowApplicationLogAccess = false;
|
settings.Security.AllowApplicationLogAccess = false;
|
||||||
settings.Security.AllowTermination = true;
|
settings.Security.AllowTermination = true;
|
||||||
|
|
|
@ -255,6 +255,7 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
|
||||||
internal const string Enabled = "zoomEnable";
|
internal const string Enabled = "zoomEnable";
|
||||||
internal const string MeetingNumber = "zoomRoom";
|
internal const string MeetingNumber = "zoomRoom";
|
||||||
internal const string Signature = "zoomToken";
|
internal const string Signature = "zoomToken";
|
||||||
|
internal const string Subject = "zoomSubject";
|
||||||
internal const string UserName = "zoomUserInfoDisplayName";
|
internal const string UserName = "zoomUserInfoDisplayName";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -126,6 +126,7 @@ namespace SafeExamBrowser.Proctoring
|
||||||
settings.Zoom.MeetingNumber = args.ZoomMeetingNumber;
|
settings.Zoom.MeetingNumber = args.ZoomMeetingNumber;
|
||||||
settings.Zoom.Password = args.ZoomPassword;
|
settings.Zoom.Password = args.ZoomPassword;
|
||||||
settings.Zoom.Signature = args.ZoomSignature;
|
settings.Zoom.Signature = args.ZoomSignature;
|
||||||
|
settings.Zoom.Subject = args.ZoomSubject;
|
||||||
settings.Zoom.UserName = args.ZoomUserName;
|
settings.Zoom.UserName = args.ZoomUserName;
|
||||||
|
|
||||||
StopProctoring();
|
StopProctoring();
|
||||||
|
@ -140,6 +141,10 @@ namespace SafeExamBrowser.Proctoring
|
||||||
settings.JitsiMeet.ReceiveAudio = receiveAudio;
|
settings.JitsiMeet.ReceiveAudio = receiveAudio;
|
||||||
settings.JitsiMeet.ReceiveVideo = receiveVideo;
|
settings.JitsiMeet.ReceiveVideo = receiveVideo;
|
||||||
|
|
||||||
|
settings.Zoom.AllowChat = allowChat;
|
||||||
|
settings.Zoom.ReceiveAudio = receiveAudio;
|
||||||
|
settings.Zoom.ReceiveVideo = receiveVideo;
|
||||||
|
|
||||||
if (allowChat || receiveVideo)
|
if (allowChat || receiveVideo)
|
||||||
{
|
{
|
||||||
settings.WindowVisibility = WindowVisibility.AllowToHide;
|
settings.WindowVisibility = WindowVisibility.AllowToHide;
|
||||||
|
@ -149,7 +154,6 @@ namespace SafeExamBrowser.Proctoring
|
||||||
settings.WindowVisibility = windowVisibility;
|
settings.WindowVisibility = windowVisibility;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: This is apparently not necessary for Zoom, there we can enable / disable the options via API call!
|
|
||||||
StopProctoring();
|
StopProctoring();
|
||||||
StartProctoring();
|
StartProctoring();
|
||||||
}
|
}
|
||||||
|
@ -176,7 +180,7 @@ namespace SafeExamBrowser.Proctoring
|
||||||
});
|
});
|
||||||
|
|
||||||
window = uiFactory.CreateProctoringWindow(control);
|
window = uiFactory.CreateProctoringWindow(control);
|
||||||
window.SetTitle(settings.JitsiMeet.Enabled ? settings.JitsiMeet.Subject : settings.Zoom.UserName);
|
window.SetTitle(settings.JitsiMeet.Enabled ? settings.JitsiMeet.Subject : settings.Zoom.Subject);
|
||||||
window.Show();
|
window.Show();
|
||||||
|
|
||||||
if (settings.WindowVisibility == WindowVisibility.AllowToShow || settings.WindowVisibility == WindowVisibility.Hidden)
|
if (settings.WindowVisibility == WindowVisibility.AllowToShow || settings.WindowVisibility == WindowVisibility.Hidden)
|
||||||
|
@ -203,7 +207,15 @@ namespace SafeExamBrowser.Proctoring
|
||||||
{
|
{
|
||||||
control.Dispatcher.Invoke(() =>
|
control.Dispatcher.Invoke(() =>
|
||||||
{
|
{
|
||||||
control.ExecuteScriptAsync("api.executeCommand('hangup'); api.dispose();");
|
if (settings.JitsiMeet.Enabled)
|
||||||
|
{
|
||||||
|
control.ExecuteScriptAsync("api.executeCommand('hangup'); api.dispose();");
|
||||||
|
}
|
||||||
|
else if (settings.Zoom.Enabled)
|
||||||
|
{
|
||||||
|
control.ExecuteScriptAsync("ZoomMtg.leaveMeeting({});");
|
||||||
|
}
|
||||||
|
|
||||||
window.Close();
|
window.Close();
|
||||||
control = default(ProctoringControl);
|
control = default(ProctoringControl);
|
||||||
window = default(IProctoringWindow);
|
window = default(IProctoringWindow);
|
||||||
|
@ -242,6 +254,7 @@ namespace SafeExamBrowser.Proctoring
|
||||||
}
|
}
|
||||||
else if (settings.Zoom.Enabled)
|
else if (settings.Zoom.Enabled)
|
||||||
{
|
{
|
||||||
|
html = html.Replace("'%_ALLOW_CHAT_%'", settings.Zoom.AllowChat ? "true" : "false");
|
||||||
html = html.Replace("%%_API_KEY_%%", settings.Zoom.ApiKey);
|
html = html.Replace("%%_API_KEY_%%", settings.Zoom.ApiKey);
|
||||||
html = html.Replace("%%_API_SECRET_%%", settings.Zoom.ApiSecret);
|
html = html.Replace("%%_API_SECRET_%%", settings.Zoom.ApiSecret);
|
||||||
html = html.Replace("%%_MEETING_NUMBER_%%", settings.Zoom.MeetingNumber);
|
html = html.Replace("%%_MEETING_NUMBER_%%", settings.Zoom.MeetingNumber);
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
const API_SECRET = '%%_API_SECRET_%%';
|
const API_SECRET = '%%_API_SECRET_%%';
|
||||||
const ATTENDEE = 0;
|
const ATTENDEE = 0;
|
||||||
|
|
||||||
|
var audioJoin = 0;
|
||||||
var configuration = {
|
var configuration = {
|
||||||
leaveUrl: 'doesnotexist',
|
leaveUrl: 'doesnotexist',
|
||||||
meetingNumber: '%%_MEETING_NUMBER_%%',
|
meetingNumber: '%%_MEETING_NUMBER_%%',
|
||||||
|
@ -26,6 +27,39 @@
|
||||||
userName: '%%_USER_NAME_%%'
|
userName: '%%_USER_NAME_%%'
|
||||||
};
|
};
|
||||||
var signature = '%%_SIGNATURE_%%';
|
var signature = '%%_SIGNATURE_%%';
|
||||||
|
var videoJoin = 0;
|
||||||
|
|
||||||
|
function initializeUserInterface(_) {
|
||||||
|
var audioButton = document.getElementsByClassName('join-audio-by-voip__join-btn')[0];
|
||||||
|
var audioContainer = document.getElementsByClassName('join-audio-container')[0];
|
||||||
|
var leave = document.getElementsByClassName('footer__leave-btn-container')[0];
|
||||||
|
var videoButton = document.getElementsByClassName('send-video-container__btn')[0];
|
||||||
|
var videoContainer = document.getElementsByClassName('send-video-container')[0];
|
||||||
|
|
||||||
|
if (audioButton && audioJoin < 100) {
|
||||||
|
audioButton.click();
|
||||||
|
audioJoin++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (audioContainer) {
|
||||||
|
audioContainer.style.visibility = "hidden";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (leave) {
|
||||||
|
leave.style.visibility = "hidden";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (videoButton && videoJoin < 100) {
|
||||||
|
videoButton.click();
|
||||||
|
videoJoin++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (videoContainer) {
|
||||||
|
videoContainer.style.visibility = "hidden";
|
||||||
|
}
|
||||||
|
|
||||||
|
requestAnimationFrame(initializeUserInterface);
|
||||||
|
};
|
||||||
|
|
||||||
if (!ZoomMtg.checkSystemRequirements()) {
|
if (!ZoomMtg.checkSystemRequirements()) {
|
||||||
alert('This system does not meet the necessary requirements for Zoom!');
|
alert('This system does not meet the necessary requirements for Zoom!');
|
||||||
|
@ -42,46 +76,47 @@
|
||||||
apiSecret: API_SECRET,
|
apiSecret: API_SECRET,
|
||||||
role: configuration.role,
|
role: configuration.role,
|
||||||
error: function (res) {
|
error: function (res) {
|
||||||
alert(`Failed to generate signature: ${JSON.stringify(res)}`)
|
alert(`Failed to generate signature: ${JSON.stringify(res)}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ZoomMtg.init({
|
ZoomMtg.init({
|
||||||
leaveUrl: configuration.leaveUrl,
|
audioPanelAlwaysOpen: false,
|
||||||
showMeetingHeader: true,
|
disableCallOut: true,
|
||||||
disableInvite: false,
|
disableInvite: true,
|
||||||
disableCallOut: false,
|
|
||||||
disableRecord: false,
|
|
||||||
disableJoinAudio: false,
|
disableJoinAudio: false,
|
||||||
audioPanelAlwaysOpen: true,
|
disableRecord: true,
|
||||||
showPureSharingContent: false,
|
disableReport: true,
|
||||||
isSupportAV: true,
|
disableVoIP: false,
|
||||||
isSupportChat: true,
|
leaveUrl: configuration.leaveUrl,
|
||||||
isSupportQA: true,
|
|
||||||
isSupportCC: true,
|
|
||||||
screenShare: true,
|
|
||||||
videoDrag: true,
|
|
||||||
sharingMode: 'both',
|
|
||||||
videoHeader: true,
|
|
||||||
isLockBottom: true,
|
isLockBottom: true,
|
||||||
isSupportNonverbal: true,
|
|
||||||
isShowJoiningErrorDialog: true,
|
isShowJoiningErrorDialog: true,
|
||||||
inviteUrlFormat: '',
|
isSupportAV: true,
|
||||||
|
isSupportBreakout: false,
|
||||||
|
isSupportChat: '%_ALLOW_CHAT_%',
|
||||||
|
isSupportCC: false,
|
||||||
|
isSupportPolling: false,
|
||||||
|
isSupportQA: false,
|
||||||
|
isSupportNonverbal: false,
|
||||||
|
screenShare: false,
|
||||||
|
sharingMode: 'both',
|
||||||
|
showMeetingHeader: true,
|
||||||
|
showPureSharingContent: false,
|
||||||
|
videoDrag: true,
|
||||||
|
videoHeader: true,
|
||||||
meetingInfo: [
|
meetingInfo: [
|
||||||
'topic',
|
'topic',
|
||||||
'host',
|
'host',
|
||||||
'mn',
|
|
||||||
'pwd',
|
|
||||||
'telPwd',
|
|
||||||
'invite',
|
|
||||||
'participant',
|
'participant',
|
||||||
'dc'
|
//'mn',
|
||||||
|
//'pwd',
|
||||||
|
//'telPwd',
|
||||||
|
//'invite',
|
||||||
|
//'dc'
|
||||||
],
|
],
|
||||||
disableVoIP: false,
|
|
||||||
disableReport: false,
|
|
||||||
error: function (res) {
|
error: function (res) {
|
||||||
alert(`Failed to initialize meeting: ${JSON.stringify(res)}`)
|
alert(`Failed to initialize meeting: ${JSON.stringify(res)}`);
|
||||||
},
|
},
|
||||||
success: function () {
|
success: function () {
|
||||||
ZoomMtg.join({
|
ZoomMtg.join({
|
||||||
|
@ -91,11 +126,14 @@
|
||||||
signature: signature,
|
signature: signature,
|
||||||
userName: configuration.userName,
|
userName: configuration.userName,
|
||||||
error: function (res) {
|
error: function (res) {
|
||||||
alert(`Failed to join meeting: ${JSON.stringify(res)}`)
|
alert(`Failed to join meeting: ${JSON.stringify(res)}`);
|
||||||
|
},
|
||||||
|
success: function (res) {
|
||||||
|
requestAnimationFrame(initializeUserInterface);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
|
@ -20,6 +20,7 @@ namespace SafeExamBrowser.Server.Contracts.Events
|
||||||
public string ZoomMeetingNumber { get; set; }
|
public string ZoomMeetingNumber { get; set; }
|
||||||
public string ZoomPassword { get; set; }
|
public string ZoomPassword { get; set; }
|
||||||
public string ZoomSignature { get; set; }
|
public string ZoomSignature { get; set; }
|
||||||
|
public string ZoomSubject { get; set; }
|
||||||
public string ZoomUserName { get; set; }
|
public string ZoomUserName { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -206,6 +206,7 @@ namespace SafeExamBrowser.Server
|
||||||
attributes.Instruction.ZoomMeetingNumber = attributesJson["zoomRoom"].Value<string>();
|
attributes.Instruction.ZoomMeetingNumber = attributesJson["zoomRoom"].Value<string>();
|
||||||
attributes.Instruction.ZoomPassword = attributesJson["zoomMeetingKey"].Value<string>();
|
attributes.Instruction.ZoomPassword = attributesJson["zoomMeetingKey"].Value<string>();
|
||||||
attributes.Instruction.ZoomSignature = attributesJson["zoomToken"].Value<string>();
|
attributes.Instruction.ZoomSignature = attributesJson["zoomToken"].Value<string>();
|
||||||
|
attributes.Instruction.ZoomSubject = attributesJson["zoomSubject"].Value<string>();
|
||||||
attributes.Instruction.ZoomUserName = attributesJson["zoomUserName"].Value<string>();
|
attributes.Instruction.ZoomUserName = attributesJson["zoomUserName"].Value<string>();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,11 @@ namespace SafeExamBrowser.Settings.Proctoring
|
||||||
[Serializable]
|
[Serializable]
|
||||||
public class ZoomSettings
|
public class ZoomSettings
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether the user can use the chat.
|
||||||
|
/// </summary>
|
||||||
|
public bool AllowChat { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The API key to be used for authentication.
|
/// The API key to be used for authentication.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -41,11 +46,26 @@ namespace SafeExamBrowser.Settings.Proctoring
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Password { get; set; }
|
public string Password { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether the user may receive the video stream of other meeting participants.
|
||||||
|
/// </summary>
|
||||||
|
public bool ReceiveAudio { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether the user may receive the audio stream of other meeting participants.
|
||||||
|
/// </summary>
|
||||||
|
public bool ReceiveVideo { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The signature to be used for authentication.
|
/// The signature to be used for authentication.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Signature { get; set; }
|
public string Signature { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The subject of the meeting.
|
||||||
|
/// </summary>
|
||||||
|
public string Subject { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The user name to be used for the meeting.
|
/// The user name to be used for the meeting.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
Loading…
Reference in a new issue