From 52217fa477fce4db18db30580f98c73d3ca98a5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damian=20B=C3=BCchel?= Date: Wed, 10 Mar 2021 21:26:45 +0100 Subject: [PATCH] SEBWIN-449: Implemented basic functionality of Jitsi Meet. --- SafeExamBrowser.Client/CompositionRoot.cs | 2 +- .../DataMapping/ProctoringDataMapper.cs | 45 ++++++++- .../ConfigurationData/Keys.cs | 4 + .../JitsiMeet/index.html | 22 +++++ .../ProctoringControl.cs | 35 ++++++- .../ProctoringController.cs | 59 ++++++++++-- .../SafeExamBrowser.Proctoring.csproj | 8 +- SafeExamBrowser.Proctoring/Zoom/index.html | 95 ++++++++++++++++++- SafeExamBrowser.Proctoring/Zoom/index.js | 95 ------------------- .../Proctoring/JitsiMeetSettings.cs | 44 +++++++++ .../Proctoring/ProctoringSettings.cs | 16 ++++ .../Proctoring/ZoomSettings.cs | 44 +++++++++ .../SafeExamBrowser.Settings.csproj | 2 + 13 files changed, 358 insertions(+), 113 deletions(-) create mode 100644 SafeExamBrowser.Proctoring/JitsiMeet/index.html delete mode 100644 SafeExamBrowser.Proctoring/Zoom/index.js create mode 100644 SafeExamBrowser.Settings/Proctoring/JitsiMeetSettings.cs create mode 100644 SafeExamBrowser.Settings/Proctoring/ZoomSettings.cs diff --git a/SafeExamBrowser.Client/CompositionRoot.cs b/SafeExamBrowser.Client/CompositionRoot.cs index 2ff52fbf..3c0b045e 100644 --- a/SafeExamBrowser.Client/CompositionRoot.cs +++ b/SafeExamBrowser.Client/CompositionRoot.cs @@ -249,7 +249,7 @@ namespace SafeExamBrowser.Client private IOperation BuildProctoringOperation() { - var controller = new ProctoringController(uiFactory); + var controller = new ProctoringController(ModuleLogger(nameof(ProctoringController)), uiFactory); var operation = new ProctoringOperation(context, logger, controller); context.ProctoringController = controller; diff --git a/SafeExamBrowser.Configuration/ConfigurationData/DataMapping/ProctoringDataMapper.cs b/SafeExamBrowser.Configuration/ConfigurationData/DataMapping/ProctoringDataMapper.cs index 23097435..0627fea0 100644 --- a/SafeExamBrowser.Configuration/ConfigurationData/DataMapping/ProctoringDataMapper.cs +++ b/SafeExamBrowser.Configuration/ConfigurationData/DataMapping/ProctoringDataMapper.cs @@ -17,7 +17,50 @@ namespace SafeExamBrowser.Configuration.ConfigurationData.DataMapping { switch (key) { - // TODO + case Keys.Proctoring.JitsiMeet.RoomName: + MapJitsiMeetRoomName(settings, value); + break; + case Keys.Proctoring.JitsiMeet.ServerUrl: + MapJitsiMeetServerUrl(settings, value); + break; + case Keys.Proctoring.JitsiMeet.Subject: + MapJitsiMeetSubject(settings, value); + break; + case Keys.Proctoring.JitsiMeet.Token: + MapJitsiMeetToken(settings, value); + break; + } + } + + private void MapJitsiMeetRoomName(AppSettings settings, object value) + { + if (value is string name) + { + settings.Proctoring.JitsiMeet.RoomName = name; + } + } + + private void MapJitsiMeetServerUrl(AppSettings settings, object value) + { + if (value is string url) + { + settings.Proctoring.JitsiMeet.ServerUrl = url; + } + } + + private void MapJitsiMeetSubject(AppSettings settings, object value) + { + if (value is string subject) + { + settings.Proctoring.JitsiMeet.Subject = subject; + } + } + + private void MapJitsiMeetToken(AppSettings settings, object value) + { + if (value is string token) + { + settings.Proctoring.JitsiMeet.Token = token; } } diff --git a/SafeExamBrowser.Configuration/ConfigurationData/Keys.cs b/SafeExamBrowser.Configuration/ConfigurationData/Keys.cs index c9b9d4de..899f464f 100644 --- a/SafeExamBrowser.Configuration/ConfigurationData/Keys.cs +++ b/SafeExamBrowser.Configuration/ConfigurationData/Keys.cs @@ -219,6 +219,10 @@ namespace SafeExamBrowser.Configuration.ConfigurationData internal static class JitsiMeet { internal const string Enabled = "jitsiMeetEnable"; + internal const string RoomName = "jitsiMeetRoom"; + internal const string ServerUrl = "jitsiMeetServerURL"; + internal const string Subject = "jitsiMeetSubject"; + internal const string Token = "jitsiMeetToken"; } internal static class Zoom diff --git a/SafeExamBrowser.Proctoring/JitsiMeet/index.html b/SafeExamBrowser.Proctoring/JitsiMeet/index.html new file mode 100644 index 00000000..5fc8af18 --- /dev/null +++ b/SafeExamBrowser.Proctoring/JitsiMeet/index.html @@ -0,0 +1,22 @@ + + + + + +
+ + + + \ No newline at end of file diff --git a/SafeExamBrowser.Proctoring/ProctoringControl.cs b/SafeExamBrowser.Proctoring/ProctoringControl.cs index d9600a8d..bc4cfb68 100644 --- a/SafeExamBrowser.Proctoring/ProctoringControl.cs +++ b/SafeExamBrowser.Proctoring/ProctoringControl.cs @@ -6,17 +6,46 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -using System; +using Microsoft.Web.WebView2.Core; using Microsoft.Web.WebView2.Wpf; +using SafeExamBrowser.Logging.Contracts; using SafeExamBrowser.UserInterface.Contracts.Proctoring; namespace SafeExamBrowser.Proctoring { internal class ProctoringControl : WebView2, IProctoringControl { - internal ProctoringControl() + private readonly ILogger logger; + + internal ProctoringControl(ILogger logger) { - Source = new Uri("https://www.microsoft.com"); + this.logger = logger; + CoreWebView2InitializationCompleted += ProctoringControl_CoreWebView2InitializationCompleted; + } + + private void CoreWebView2_PermissionRequested(object sender, CoreWebView2PermissionRequestedEventArgs e) + { + if (e.PermissionKind == CoreWebView2PermissionKind.Camera || e.PermissionKind == CoreWebView2PermissionKind.Microphone) + { + logger.Info($"Granted access to {e.PermissionKind}."); + } + else + { + logger.Info($"Denied access to {e.PermissionKind}."); + } + } + + private void ProctoringControl_CoreWebView2InitializationCompleted(object sender, CoreWebView2InitializationCompletedEventArgs e) + { + if (e.IsSuccess) + { + CoreWebView2.PermissionRequested += CoreWebView2_PermissionRequested; + logger.Info("Successfully initialized."); + } + else + { + logger.Error("Failed to initialize!", e.InitializationException); + } } } } diff --git a/SafeExamBrowser.Proctoring/ProctoringController.cs b/SafeExamBrowser.Proctoring/ProctoringController.cs index b98acf87..834e5c90 100644 --- a/SafeExamBrowser.Proctoring/ProctoringController.cs +++ b/SafeExamBrowser.Proctoring/ProctoringController.cs @@ -6,6 +6,10 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +using System; +using System.IO; +using System.Reflection; +using SafeExamBrowser.Logging.Contracts; using SafeExamBrowser.Proctoring.Contracts; using SafeExamBrowser.Settings.Proctoring; using SafeExamBrowser.UserInterface.Contracts; @@ -15,31 +19,72 @@ namespace SafeExamBrowser.Proctoring { public class ProctoringController : IProctoringController { + private readonly IModuleLogger logger; private readonly IUserInterfaceFactory uiFactory; private IProctoringWindow window; - public ProctoringController(IUserInterfaceFactory uiFactory) + public ProctoringController(IModuleLogger logger, IUserInterfaceFactory uiFactory) { + this.logger = logger; this.uiFactory = uiFactory; } public void Initialize(ProctoringSettings settings) { - var control = new ProctoringControl(); + if (settings.JitsiMeet.Enabled || settings.Zoom.Enabled) + { + var control = new ProctoringControl(logger.CloneFor(nameof(ProctoringControl))); - window = uiFactory.CreateProctoringWindow(control); - window.Show(); + control.EnsureCoreWebView2Async().ContinueWith(_ => + { + control.Dispatcher.Invoke(() => control.NavigateToString(LoadContent(settings))); + }); - // TODO - //var content = load Zoom page, replace //INDEX_JS//; + window = uiFactory.CreateProctoringWindow(control); + window.Show(); - //control.NavigateToString(content); + logger.Info($"Initialized proctoring with {(settings.JitsiMeet.Enabled ? "Jitsi Meet" : "Zoom")}."); + } + else + { + logger.Warn("Failed to initialize remote proctoring because no provider is enabled in the active configuration."); + } } public void Terminate() { window?.Close(); } + + private string LoadContent(ProctoringSettings settings) + { + var provider = settings.JitsiMeet.Enabled ? "JitsiMeet" : "Zoom"; + var assembly = Assembly.GetAssembly(typeof(ProctoringController)); + var path = $"{typeof(ProctoringController).Namespace}.{provider}.index.html"; + + using (var stream = assembly.GetManifestResourceStream(path)) + using (var reader = new StreamReader(stream)) + { + var html = reader.ReadToEnd(); + + if (settings.JitsiMeet.Enabled) + { + html = html.Replace("%%_DOMAIN_%%", settings.JitsiMeet.ServerUrl); + html = html.Replace("%%_ROOM_NAME_%%", settings.JitsiMeet.RoomName); + html = html.Replace("%%_SUBJECT_%%", settings.JitsiMeet.Subject); + html = html.Replace("%%_TOKEN_%%", settings.JitsiMeet.Token); + } + else if (settings.Zoom.Enabled) + { + html = html.Replace("%%_API_KEY_%%", settings.Zoom.ApiKey); + html = html.Replace("%%_API_SECRET_%%", settings.Zoom.ApiSecret); + html = html.Replace("123456789", Convert.ToString(settings.Zoom.MeetingNumber)); + html = html.Replace("%%_USER_NAME_%%", settings.Zoom.UserName); + } + + return html; + } + } } } diff --git a/SafeExamBrowser.Proctoring/SafeExamBrowser.Proctoring.csproj b/SafeExamBrowser.Proctoring/SafeExamBrowser.Proctoring.csproj index 1c1bf007..c81eeb22 100644 --- a/SafeExamBrowser.Proctoring/SafeExamBrowser.Proctoring.csproj +++ b/SafeExamBrowser.Proctoring/SafeExamBrowser.Proctoring.csproj @@ -95,12 +95,10 @@ - - - - - + + + diff --git a/SafeExamBrowser.Proctoring/Zoom/index.html b/SafeExamBrowser.Proctoring/Zoom/index.html index e6151f97..a29ac0e8 100644 --- a/SafeExamBrowser.Proctoring/Zoom/index.html +++ b/SafeExamBrowser.Proctoring/Zoom/index.html @@ -14,7 +14,100 @@ \ No newline at end of file diff --git a/SafeExamBrowser.Proctoring/Zoom/index.js b/SafeExamBrowser.Proctoring/Zoom/index.js deleted file mode 100644 index 9adba733..00000000 --- a/SafeExamBrowser.Proctoring/Zoom/index.js +++ /dev/null @@ -1,95 +0,0 @@ -const API_KEY = "..."; -const API_SECRET = "..."; - -console.log("Checking system requirements..."); -console.log(JSON.stringify(ZoomMtg.checkSystemRequirements())); - -console.log("Initializing Zoom..."); -ZoomMtg.setZoomJSLib('https://source.zoom.us/1.8.1/lib', '/av'); -ZoomMtg.preLoadWasm(); -ZoomMtg.prepareJssdk(); - -const config = { - meetingNumber: 123456, - leaveUrl: 'https://google.ch', - userName: 'Firstname Lastname', - userEmail: 'firstname.lastname@yoursite.com', - /* passWord: 'password', // if required */ - role: 0 // 1 for host; 0 for attendee -}; - -const signature = ZoomMtg.generateSignature({ - meetingNumber: config.meetingNumber, - apiKey: API_KEY, - apiSecret: API_SECRET, - role: config.role, - error: function (res) { - console.error("FAILED TO GENERATE SIGNATURE: " + res) - }, - success: function (res) { - console.log("Successfully generated signature."); - console.log(res.result); - }, -}); - -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: false, //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: 380 - }, - // meetingInfo: [ // optional - // 'topic', - // 'host', - // 'mn', - // 'pwd', - // 'telPwd', - // 'invite', - // 'participant', - // 'dc' - // ], - disableVoIP: false, // optional - disableReport: false, // optional - error: function(res) { - console.warn("INIT ERROR") - console.log(res) - }, - success: function() { - ZoomMtg.join({ - signature: signature, - apiKey: API_KEY, - meetingNumber: config.meetingNumber, - userName: config.userName, - /* passWord: meetConfig.passWord, */ - error(res) { - console.warn("JOIN ERROR") - console.log(res) - } - }) - } -}) diff --git a/SafeExamBrowser.Settings/Proctoring/JitsiMeetSettings.cs b/SafeExamBrowser.Settings/Proctoring/JitsiMeetSettings.cs new file mode 100644 index 00000000..ff8bb029 --- /dev/null +++ b/SafeExamBrowser.Settings/Proctoring/JitsiMeetSettings.cs @@ -0,0 +1,44 @@ +/* + * 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/. + */ + +using System; + +namespace SafeExamBrowser.Settings.Proctoring +{ + /// + /// All settings for the meeting provider Jitsi Meet. + /// + [Serializable] + public class JitsiMeetSettings + { + /// + /// Determines whether proctoring with Jitsi Meet is enabled. + /// + public bool Enabled { get; set; } + + /// + /// The name of the meeting room. + /// + public string RoomName { get; set; } + + /// + /// The URL of the Jitsi Meet server. + /// + public string ServerUrl { get; set; } + + /// + /// The subject of the meeting. + /// + public string Subject { get; set; } + + /// + /// The authentication token for the meeting. + /// + public string Token { get; set; } + } +} diff --git a/SafeExamBrowser.Settings/Proctoring/ProctoringSettings.cs b/SafeExamBrowser.Settings/Proctoring/ProctoringSettings.cs index 306c68e8..cb14ccb8 100644 --- a/SafeExamBrowser.Settings/Proctoring/ProctoringSettings.cs +++ b/SafeExamBrowser.Settings/Proctoring/ProctoringSettings.cs @@ -20,5 +20,21 @@ namespace SafeExamBrowser.Settings.Proctoring /// Determines whether the entire remote proctoring feature is enabled. /// public bool Enabled { get; set; } + + /// + /// All settings for remote proctoring with Jitsi Meet. + /// + public JitsiMeetSettings JitsiMeet { get; set; } + + /// + /// All settings for remote proctoring with Zoom. + /// + public ZoomSettings Zoom { get; set; } + + public ProctoringSettings() + { + JitsiMeet = new JitsiMeetSettings(); + Zoom = new ZoomSettings(); + } } } diff --git a/SafeExamBrowser.Settings/Proctoring/ZoomSettings.cs b/SafeExamBrowser.Settings/Proctoring/ZoomSettings.cs new file mode 100644 index 00000000..85739958 --- /dev/null +++ b/SafeExamBrowser.Settings/Proctoring/ZoomSettings.cs @@ -0,0 +1,44 @@ +/* + * 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/. + */ + +using System; + +namespace SafeExamBrowser.Settings.Proctoring +{ + /// + /// All settings for the meeting provider Zoom. + /// + [Serializable] + public class ZoomSettings + { + /// + /// The API key to be used for authentication. + /// + public string ApiKey { get; set; } + + /// + /// The API secret to be used for authentication. + /// + public string ApiSecret { get; set; } + + /// + /// Determines whether proctoring with Zoom is enabled. + /// + public bool Enabled { get; set; } + + /// + /// The number of the meeting. + /// + public int MeetingNumber { get; set; } + + /// + /// The user name to be used for the meeting. + /// + public string UserName { get; set; } + } +} diff --git a/SafeExamBrowser.Settings/SafeExamBrowser.Settings.csproj b/SafeExamBrowser.Settings/SafeExamBrowser.Settings.csproj index 5f85298c..6780fbb9 100644 --- a/SafeExamBrowser.Settings/SafeExamBrowser.Settings.csproj +++ b/SafeExamBrowser.Settings/SafeExamBrowser.Settings.csproj @@ -71,7 +71,9 @@ + +