SEBWIN-405: Prepared infrastructure in client for server functionality.
This commit is contained in:
parent
ef13cfe9c5
commit
bc06a0c985
20 changed files with 216 additions and 30 deletions
|
@ -12,6 +12,7 @@ using SafeExamBrowser.Applications.Contracts;
|
|||
using SafeExamBrowser.Browser.Contracts;
|
||||
using SafeExamBrowser.Communication.Contracts.Hosts;
|
||||
using SafeExamBrowser.Configuration.Contracts;
|
||||
using SafeExamBrowser.Server.Contracts;
|
||||
using SafeExamBrowser.Settings;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Shell;
|
||||
|
||||
|
@ -47,6 +48,11 @@ namespace SafeExamBrowser.Client
|
|||
/// </summary>
|
||||
internal IClientHost ClientHost { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The server proxy, if the current session mode is <see cref="SessionMode.Server"/>.
|
||||
/// </summary>
|
||||
internal IServerProxy Server { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The identifier of the current session.
|
||||
/// </summary>
|
||||
|
|
|
@ -26,6 +26,7 @@ using SafeExamBrowser.Logging.Contracts;
|
|||
using SafeExamBrowser.Monitoring.Contracts.Applications;
|
||||
using SafeExamBrowser.Monitoring.Contracts.Display;
|
||||
using SafeExamBrowser.Monitoring.Contracts.System;
|
||||
using SafeExamBrowser.Server.Contracts;
|
||||
using SafeExamBrowser.Settings;
|
||||
using SafeExamBrowser.UserInterface.Contracts;
|
||||
using SafeExamBrowser.UserInterface.Contracts.FileSystemDialog;
|
||||
|
@ -60,6 +61,7 @@ namespace SafeExamBrowser.Client
|
|||
|
||||
private IBrowserApplication Browser => context.Browser;
|
||||
private IClientHost ClientHost => context.ClientHost;
|
||||
private IServerProxy Server => context.Server;
|
||||
private AppSettings Settings => context.Settings;
|
||||
|
||||
internal ClientController(
|
||||
|
@ -196,6 +198,11 @@ namespace SafeExamBrowser.Client
|
|||
{
|
||||
activator.Activated += TerminationActivator_Activated;
|
||||
}
|
||||
|
||||
if (Server != null)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
||||
private void DeregisterEvents()
|
||||
|
@ -222,6 +229,11 @@ namespace SafeExamBrowser.Client
|
|||
ClientHost.Shutdown -= ClientHost_Shutdown;
|
||||
}
|
||||
|
||||
if (Server != null)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
foreach (var activator in context.Activators.OfType<ITerminationActivator>())
|
||||
{
|
||||
activator.Activated -= TerminationActivator_Activated;
|
||||
|
|
|
@ -31,6 +31,7 @@ using SafeExamBrowser.Monitoring.Display;
|
|||
using SafeExamBrowser.Monitoring.Keyboard;
|
||||
using SafeExamBrowser.Monitoring.Mouse;
|
||||
using SafeExamBrowser.Monitoring.System;
|
||||
using SafeExamBrowser.Server;
|
||||
using SafeExamBrowser.Settings.Logging;
|
||||
using SafeExamBrowser.Settings.UserInterface;
|
||||
using SafeExamBrowser.SystemComponents;
|
||||
|
@ -118,6 +119,7 @@ namespace SafeExamBrowser.Client
|
|||
operations.Enqueue(new SystemMonitorOperation(context, systemMonitor, logger));
|
||||
operations.Enqueue(new LazyInitializationOperation(BuildShellOperation));
|
||||
operations.Enqueue(new LazyInitializationOperation(BuildBrowserOperation));
|
||||
operations.Enqueue(new LazyInitializationOperation(BuildServerOperation));
|
||||
operations.Enqueue(new ClipboardOperation(context, logger, nativeMethods));
|
||||
|
||||
var sequence = new OperationSequence(logger, operations);
|
||||
|
@ -237,6 +239,14 @@ namespace SafeExamBrowser.Client
|
|||
return operation;
|
||||
}
|
||||
|
||||
private IOperation BuildServerOperation()
|
||||
{
|
||||
var server = new ServerProxy(context.AppConfig, logger);
|
||||
var operation = new ServerOperation(actionCenter, context, logger, server, taskbar);
|
||||
|
||||
return operation;
|
||||
}
|
||||
|
||||
private IOperation BuildShellOperation()
|
||||
{
|
||||
var aboutInfo = new AboutNotificationInfo(text);
|
||||
|
|
75
SafeExamBrowser.Client/Operations/ServerOperation.cs
Normal file
75
SafeExamBrowser.Client/Operations/ServerOperation.cs
Normal file
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* Copyright (c) 2020 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 SafeExamBrowser.Core.Contracts.OperationModel;
|
||||
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Server.Contracts;
|
||||
using SafeExamBrowser.Settings;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Shell;
|
||||
|
||||
namespace SafeExamBrowser.Client.Operations
|
||||
{
|
||||
internal class ServerOperation : ClientOperation
|
||||
{
|
||||
private readonly IActionCenter actionCenter;
|
||||
private readonly ILogger logger;
|
||||
private readonly IServerProxy server;
|
||||
private readonly ITaskbar taskbar;
|
||||
|
||||
public override event ActionRequiredEventHandler ActionRequired { add { } remove { } }
|
||||
public override event StatusChangedEventHandler StatusChanged;
|
||||
|
||||
public ServerOperation(
|
||||
IActionCenter actionCenter,
|
||||
ClientContext context,
|
||||
ILogger logger,
|
||||
IServerProxy server,
|
||||
ITaskbar taskbar) : base(context)
|
||||
{
|
||||
this.actionCenter = actionCenter;
|
||||
this.logger = logger;
|
||||
this.server = server;
|
||||
this.taskbar = taskbar;
|
||||
}
|
||||
|
||||
public override OperationResult Perform()
|
||||
{
|
||||
var result = OperationResult.Success;
|
||||
|
||||
if (Context.Settings.SessionMode == SessionMode.Server)
|
||||
{
|
||||
logger.Info("Initializing server...");
|
||||
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeServer);
|
||||
|
||||
server.Initialize(Context.AppConfig.ServerApi, Context.AppConfig.ServerConnectionToken, Context.AppConfig.ServerOauth2Token, Context.Settings.Server);
|
||||
|
||||
// TODO: Add action center and taskbar notifications
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override OperationResult Revert()
|
||||
{
|
||||
var result = OperationResult.Success;
|
||||
|
||||
if (Context.Settings?.SessionMode == SessionMode.Server)
|
||||
{
|
||||
logger.Info("Finalizing server...");
|
||||
StatusChanged?.Invoke(TextKey.OperationStatus_FinalizeServer);
|
||||
|
||||
// TODO: Stop sending pings and logs (or in controller?)
|
||||
// TODO: Stop action center and taskbar notifications
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -94,6 +94,7 @@
|
|||
<Compile Include="Operations\KeyboardInterceptorOperation.cs" />
|
||||
<Compile Include="Operations\MouseInterceptorOperation.cs" />
|
||||
<Compile Include="Operations\ApplicationOperation.cs" />
|
||||
<Compile Include="Operations\ServerOperation.cs" />
|
||||
<Compile Include="Operations\ShellOperation.cs" />
|
||||
<Compile Include="Operations\SystemMonitorOperation.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs">
|
||||
|
@ -193,6 +194,14 @@
|
|||
<Project>{EF563531-4EB5-44B9-A5EC-D6D6F204469B}</Project>
|
||||
<Name>SafeExamBrowser.Monitoring</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SafeExamBrowser.Server.Contracts\SafeExamBrowser.Server.Contracts.csproj">
|
||||
<Project>{db701e6f-bddc-4cec-b662-335a9dc11809}</Project>
|
||||
<Name>SafeExamBrowser.Server.Contracts</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SafeExamBrowser.Server\SafeExamBrowser.Server.csproj">
|
||||
<Project>{46edbde0-58b4-4725-9783-0c55c3d49c0c}</Project>
|
||||
<Name>SafeExamBrowser.Server</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SafeExamBrowser.Settings\SafeExamBrowser.Settings.csproj">
|
||||
<Project>{30b2d907-5861-4f39-abad-c4abf1b3470e}</Project>
|
||||
<Name>SafeExamBrowser.Settings</Name>
|
||||
|
|
|
@ -151,6 +151,21 @@ namespace SafeExamBrowser.Configuration.Contracts
|
|||
/// </summary>
|
||||
public string SebUriSchemeSecure { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The server API as JSON string.
|
||||
/// </summary>
|
||||
public string ServerApi { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The connection token for a server.
|
||||
/// </summary>
|
||||
public string ServerConnectionToken { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The OAuth2 token for a server.
|
||||
/// </summary>
|
||||
public string ServerOauth2Token { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The communication address of the service component.
|
||||
/// </summary>
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
using System.Collections.Generic;
|
||||
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
|
||||
using SafeExamBrowser.Server.Contracts;
|
||||
using SafeExamBrowser.Server.Contracts.Data;
|
||||
|
||||
namespace SafeExamBrowser.Runtime.Operations.Events
|
||||
{
|
||||
|
|
|
@ -15,6 +15,7 @@ using SafeExamBrowser.I18n.Contracts;
|
|||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Runtime.Operations.Events;
|
||||
using SafeExamBrowser.Server.Contracts;
|
||||
using SafeExamBrowser.Server.Contracts.Data;
|
||||
using SafeExamBrowser.Settings;
|
||||
using SafeExamBrowser.SystemComponents.Contracts;
|
||||
|
||||
|
@ -53,7 +54,7 @@ namespace SafeExamBrowser.Runtime.Operations
|
|||
|
||||
server.Initialize(Context.Next.Settings.Server);
|
||||
|
||||
var (abort, fallback, success) = TryPerformWithFallback(() => server.Connect(), out var token);
|
||||
var (abort, fallback, success) = TryPerformWithFallback(() => server.Connect());
|
||||
|
||||
if (success)
|
||||
{
|
||||
|
@ -69,12 +70,16 @@ namespace SafeExamBrowser.Runtime.Operations
|
|||
|
||||
if (success)
|
||||
{
|
||||
var info = server.GetConnectionInfo();
|
||||
var status = TryLoadSettings(uri, UriSource.Server, out _, out var settings);
|
||||
|
||||
fileSystem.Delete(uri.LocalPath);
|
||||
|
||||
if (status == LoadStatus.Success)
|
||||
{
|
||||
Context.Next.AppConfig.ServerApi = info.Api;
|
||||
Context.Next.AppConfig.ServerConnectionToken = info.ConnectionToken;
|
||||
Context.Next.AppConfig.ServerOauth2Token = info.Oauth2Token;
|
||||
Context.Next.Settings = settings;
|
||||
Context.Next.Settings.Browser.StartUrl = exam.Url;
|
||||
result = OperationResult.Success;
|
||||
|
@ -121,7 +126,7 @@ namespace SafeExamBrowser.Runtime.Operations
|
|||
|
||||
public override OperationResult Revert()
|
||||
{
|
||||
var result = OperationResult.Failed;
|
||||
var result = OperationResult.Success;
|
||||
|
||||
if (Context.Current?.Settings.SessionMode == SessionMode.Server)
|
||||
{
|
||||
|
@ -139,10 +144,6 @@ namespace SafeExamBrowser.Runtime.Operations
|
|||
result = OperationResult.Failed;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result = OperationResult.Success;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
31
SafeExamBrowser.Server.Contracts/Data/ConnectionInfo.cs
Normal file
31
SafeExamBrowser.Server.Contracts/Data/ConnectionInfo.cs
Normal file
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (c) 2020 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/.
|
||||
*/
|
||||
|
||||
namespace SafeExamBrowser.Server.Contracts.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains all information required to establish a connection with a server.
|
||||
/// </summary>
|
||||
public class ConnectionInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// The API of the server as JSON string.
|
||||
/// </summary>
|
||||
public string Api { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The connection token for authentication with the server.
|
||||
/// </summary>
|
||||
public string ConnectionToken { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The OAuth2 token for authentication with the server.
|
||||
/// </summary>
|
||||
public string Oauth2Token { get; set; }
|
||||
}
|
||||
}
|
|
@ -6,7 +6,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
namespace SafeExamBrowser.Server.Contracts
|
||||
namespace SafeExamBrowser.Server.Contracts.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a server exam.
|
|
@ -6,7 +6,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
namespace SafeExamBrowser.Server.Contracts
|
||||
namespace SafeExamBrowser.Server.Contracts.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the result of a communication with a SEB server.
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using SafeExamBrowser.Server.Contracts.Data;
|
||||
using SafeExamBrowser.Settings.Server;
|
||||
|
||||
namespace SafeExamBrowser.Server.Contracts
|
||||
|
@ -18,33 +19,42 @@ namespace SafeExamBrowser.Server.Contracts
|
|||
public interface IServerProxy
|
||||
{
|
||||
/// <summary>
|
||||
/// TODO: Return API as well or re-load in proxy instance of client?
|
||||
/// Attempts to initialize a connection to the server. If successful, returns a OAuth2 token as response value.
|
||||
/// Attempts to initialize a connection to the server.
|
||||
/// </summary>
|
||||
ServerResponse<string> Connect();
|
||||
ServerResponse Connect();
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// TODO
|
||||
/// </summary>
|
||||
ServerResponse Disconnect();
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// Retrieves a list of all currently available exams.
|
||||
/// </summary>
|
||||
ServerResponse<IEnumerable<Exam>> GetAvailableExams();
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// Retrieves the URI of the configuration file for the given exam.
|
||||
/// </summary>
|
||||
ServerResponse<Uri> GetConfigurationFor(Exam exam);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the information required to establish a connection with this server.
|
||||
/// </summary>
|
||||
ConnectionInfo GetConnectionInfo();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the server settings to be used for communication.
|
||||
/// </summary>
|
||||
void Initialize(ServerSettings settings);
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// Initializes the configuration and server settings to be used for communication.
|
||||
/// </summary>
|
||||
void Initialize(string api, string connectionToken, string oauth2Token, ServerSettings settings);
|
||||
|
||||
/// <summary>
|
||||
/// TODO
|
||||
/// </summary>
|
||||
ServerResponse SendSessionInfo(string sessionId);
|
||||
}
|
||||
|
|
|
@ -54,10 +54,11 @@
|
|||
<Reference Include="Microsoft.CSharp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Exam.cs" />
|
||||
<Compile Include="Data\ConnectionInfo.cs" />
|
||||
<Compile Include="Data\Exam.cs" />
|
||||
<Compile Include="IServerProxy.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="ServerResponse.cs" />
|
||||
<Compile Include="Data\ServerResponse.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SafeExamBrowser.Settings\SafeExamBrowser.Settings.csproj">
|
||||
|
|
|
@ -11,13 +11,9 @@ namespace SafeExamBrowser.Server.Data
|
|||
internal class ApiVersion1
|
||||
{
|
||||
public string AccessTokenEndpoint { get; set; }
|
||||
|
||||
public string HandshakeEndpoint { get; set; }
|
||||
|
||||
public string ConfigurationEndpoint { get; set; }
|
||||
|
||||
public string PingEndpoint { get; set; }
|
||||
|
||||
public string LogEndpoint { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ using Newtonsoft.Json.Linq;
|
|||
using SafeExamBrowser.Configuration.Contracts;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Server.Contracts;
|
||||
using SafeExamBrowser.Server.Contracts.Data;
|
||||
using SafeExamBrowser.Server.Data;
|
||||
using SafeExamBrowser.Settings.Server;
|
||||
|
||||
|
@ -42,7 +43,7 @@ namespace SafeExamBrowser.Server
|
|||
this.logger = logger;
|
||||
}
|
||||
|
||||
public ServerResponse<string> Connect()
|
||||
public ServerResponse Connect()
|
||||
{
|
||||
var success = TryExecute(HttpMethod.Get, settings.ApiUrl, out var response);
|
||||
var message = ToString(response);
|
||||
|
@ -73,7 +74,7 @@ namespace SafeExamBrowser.Server
|
|||
logger.Error("Failed to load server API!");
|
||||
}
|
||||
|
||||
return new ServerResponse<string>(success, oauth2Token, message);
|
||||
return new ServerResponse(success, message);
|
||||
}
|
||||
|
||||
public ServerResponse Disconnect()
|
||||
|
@ -151,6 +152,16 @@ namespace SafeExamBrowser.Server
|
|||
return new ServerResponse<Uri>(success, uri, message);
|
||||
}
|
||||
|
||||
public ConnectionInfo GetConnectionInfo()
|
||||
{
|
||||
return new ConnectionInfo
|
||||
{
|
||||
Api = JsonConvert.SerializeObject(api),
|
||||
ConnectionToken = connectionToken,
|
||||
Oauth2Token = oauth2Token
|
||||
};
|
||||
}
|
||||
|
||||
public void Initialize(ServerSettings settings)
|
||||
{
|
||||
this.settings = settings;
|
||||
|
@ -162,6 +173,15 @@ namespace SafeExamBrowser.Server
|
|||
}
|
||||
}
|
||||
|
||||
public void Initialize(string api, string connectionToken, string oauth2Token, ServerSettings settings)
|
||||
{
|
||||
this.api = JsonConvert.DeserializeObject<ApiVersion1>(api);
|
||||
this.connectionToken = connectionToken;
|
||||
this.oauth2Token = oauth2Token;
|
||||
|
||||
Initialize(settings);
|
||||
}
|
||||
|
||||
public ServerResponse SendSessionInfo(string sessionId)
|
||||
{
|
||||
return new ServerResponse(false, "TODO!");
|
||||
|
@ -324,7 +344,7 @@ namespace SafeExamBrowser.Server
|
|||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
logger.Error($"Request {request.Method} '{request.RequestUri}' did not complete within {settings.RequestTimeout}ms!");
|
||||
logger.Debug($"Request {request.Method} '{request.RequestUri}' did not complete within {settings.RequestTimeout}ms!");
|
||||
break;
|
||||
}
|
||||
catch (Exception e)
|
||||
|
|
|
@ -12,7 +12,7 @@ using SafeExamBrowser.Client.Contracts;
|
|||
using SafeExamBrowser.Configuration.Contracts;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Server.Contracts;
|
||||
using SafeExamBrowser.Server.Contracts.Data;
|
||||
using SafeExamBrowser.Settings.Browser;
|
||||
using SafeExamBrowser.SystemComponents.Contracts.Audio;
|
||||
using SafeExamBrowser.SystemComponents.Contracts.Keyboard;
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using SafeExamBrowser.Server.Contracts;
|
||||
using SafeExamBrowser.Server.Contracts.Data;
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Contracts.Windows.Data
|
||||
{
|
||||
|
|
|
@ -16,7 +16,7 @@ using SafeExamBrowser.Client.Contracts;
|
|||
using SafeExamBrowser.Configuration.Contracts;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Server.Contracts;
|
||||
using SafeExamBrowser.Server.Contracts.Data;
|
||||
using SafeExamBrowser.Settings.Browser;
|
||||
using SafeExamBrowser.SystemComponents.Contracts.Audio;
|
||||
using SafeExamBrowser.SystemComponents.Contracts.Keyboard;
|
||||
|
|
|
@ -10,7 +10,7 @@ using System.Collections.Generic;
|
|||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.Server.Contracts;
|
||||
using SafeExamBrowser.Server.Contracts.Data;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Windows;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Windows.Data;
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ using SafeExamBrowser.Client.Contracts;
|
|||
using SafeExamBrowser.Configuration.Contracts;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Server.Contracts;
|
||||
using SafeExamBrowser.Server.Contracts.Data;
|
||||
using SafeExamBrowser.Settings.Browser;
|
||||
using SafeExamBrowser.SystemComponents.Contracts.Audio;
|
||||
using SafeExamBrowser.SystemComponents.Contracts.Keyboard;
|
||||
|
|
Loading…
Reference in a new issue