SEBWIN-405: Prepared infrastructure in client for server functionality.

This commit is contained in:
Damian Büchel 2020-07-28 19:56:25 +02:00
parent ef13cfe9c5
commit bc06a0c985
20 changed files with 216 additions and 30 deletions

View file

@ -12,6 +12,7 @@ using SafeExamBrowser.Applications.Contracts;
using SafeExamBrowser.Browser.Contracts; using SafeExamBrowser.Browser.Contracts;
using SafeExamBrowser.Communication.Contracts.Hosts; using SafeExamBrowser.Communication.Contracts.Hosts;
using SafeExamBrowser.Configuration.Contracts; using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.Server.Contracts;
using SafeExamBrowser.Settings; using SafeExamBrowser.Settings;
using SafeExamBrowser.UserInterface.Contracts.Shell; using SafeExamBrowser.UserInterface.Contracts.Shell;
@ -47,6 +48,11 @@ namespace SafeExamBrowser.Client
/// </summary> /// </summary>
internal IClientHost ClientHost { get; set; } 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> /// <summary>
/// The identifier of the current session. /// The identifier of the current session.
/// </summary> /// </summary>

View file

@ -26,6 +26,7 @@ using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Monitoring.Contracts.Applications; using SafeExamBrowser.Monitoring.Contracts.Applications;
using SafeExamBrowser.Monitoring.Contracts.Display; using SafeExamBrowser.Monitoring.Contracts.Display;
using SafeExamBrowser.Monitoring.Contracts.System; using SafeExamBrowser.Monitoring.Contracts.System;
using SafeExamBrowser.Server.Contracts;
using SafeExamBrowser.Settings; using SafeExamBrowser.Settings;
using SafeExamBrowser.UserInterface.Contracts; using SafeExamBrowser.UserInterface.Contracts;
using SafeExamBrowser.UserInterface.Contracts.FileSystemDialog; using SafeExamBrowser.UserInterface.Contracts.FileSystemDialog;
@ -60,6 +61,7 @@ namespace SafeExamBrowser.Client
private IBrowserApplication Browser => context.Browser; private IBrowserApplication Browser => context.Browser;
private IClientHost ClientHost => context.ClientHost; private IClientHost ClientHost => context.ClientHost;
private IServerProxy Server => context.Server;
private AppSettings Settings => context.Settings; private AppSettings Settings => context.Settings;
internal ClientController( internal ClientController(
@ -196,6 +198,11 @@ namespace SafeExamBrowser.Client
{ {
activator.Activated += TerminationActivator_Activated; activator.Activated += TerminationActivator_Activated;
} }
if (Server != null)
{
// TODO
}
} }
private void DeregisterEvents() private void DeregisterEvents()
@ -222,6 +229,11 @@ namespace SafeExamBrowser.Client
ClientHost.Shutdown -= ClientHost_Shutdown; ClientHost.Shutdown -= ClientHost_Shutdown;
} }
if (Server != null)
{
// TODO
}
foreach (var activator in context.Activators.OfType<ITerminationActivator>()) foreach (var activator in context.Activators.OfType<ITerminationActivator>())
{ {
activator.Activated -= TerminationActivator_Activated; activator.Activated -= TerminationActivator_Activated;

View file

@ -31,6 +31,7 @@ using SafeExamBrowser.Monitoring.Display;
using SafeExamBrowser.Monitoring.Keyboard; using SafeExamBrowser.Monitoring.Keyboard;
using SafeExamBrowser.Monitoring.Mouse; using SafeExamBrowser.Monitoring.Mouse;
using SafeExamBrowser.Monitoring.System; using SafeExamBrowser.Monitoring.System;
using SafeExamBrowser.Server;
using SafeExamBrowser.Settings.Logging; using SafeExamBrowser.Settings.Logging;
using SafeExamBrowser.Settings.UserInterface; using SafeExamBrowser.Settings.UserInterface;
using SafeExamBrowser.SystemComponents; using SafeExamBrowser.SystemComponents;
@ -118,6 +119,7 @@ namespace SafeExamBrowser.Client
operations.Enqueue(new SystemMonitorOperation(context, systemMonitor, logger)); operations.Enqueue(new SystemMonitorOperation(context, systemMonitor, logger));
operations.Enqueue(new LazyInitializationOperation(BuildShellOperation)); operations.Enqueue(new LazyInitializationOperation(BuildShellOperation));
operations.Enqueue(new LazyInitializationOperation(BuildBrowserOperation)); operations.Enqueue(new LazyInitializationOperation(BuildBrowserOperation));
operations.Enqueue(new LazyInitializationOperation(BuildServerOperation));
operations.Enqueue(new ClipboardOperation(context, logger, nativeMethods)); operations.Enqueue(new ClipboardOperation(context, logger, nativeMethods));
var sequence = new OperationSequence(logger, operations); var sequence = new OperationSequence(logger, operations);
@ -237,6 +239,14 @@ namespace SafeExamBrowser.Client
return operation; 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() private IOperation BuildShellOperation()
{ {
var aboutInfo = new AboutNotificationInfo(text); var aboutInfo = new AboutNotificationInfo(text);

View 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;
}
}
}

View file

@ -94,6 +94,7 @@
<Compile Include="Operations\KeyboardInterceptorOperation.cs" /> <Compile Include="Operations\KeyboardInterceptorOperation.cs" />
<Compile Include="Operations\MouseInterceptorOperation.cs" /> <Compile Include="Operations\MouseInterceptorOperation.cs" />
<Compile Include="Operations\ApplicationOperation.cs" /> <Compile Include="Operations\ApplicationOperation.cs" />
<Compile Include="Operations\ServerOperation.cs" />
<Compile Include="Operations\ShellOperation.cs" /> <Compile Include="Operations\ShellOperation.cs" />
<Compile Include="Operations\SystemMonitorOperation.cs" /> <Compile Include="Operations\SystemMonitorOperation.cs" />
<Compile Include="Properties\AssemblyInfo.cs"> <Compile Include="Properties\AssemblyInfo.cs">
@ -193,6 +194,14 @@
<Project>{EF563531-4EB5-44B9-A5EC-D6D6F204469B}</Project> <Project>{EF563531-4EB5-44B9-A5EC-D6D6F204469B}</Project>
<Name>SafeExamBrowser.Monitoring</Name> <Name>SafeExamBrowser.Monitoring</Name>
</ProjectReference> </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"> <ProjectReference Include="..\SafeExamBrowser.Settings\SafeExamBrowser.Settings.csproj">
<Project>{30b2d907-5861-4f39-abad-c4abf1b3470e}</Project> <Project>{30b2d907-5861-4f39-abad-c4abf1b3470e}</Project>
<Name>SafeExamBrowser.Settings</Name> <Name>SafeExamBrowser.Settings</Name>

View file

@ -151,6 +151,21 @@ namespace SafeExamBrowser.Configuration.Contracts
/// </summary> /// </summary>
public string SebUriSchemeSecure { get; set; } 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> /// <summary>
/// The communication address of the service component. /// The communication address of the service component.
/// </summary> /// </summary>

View file

@ -8,7 +8,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using SafeExamBrowser.Core.Contracts.OperationModel.Events; using SafeExamBrowser.Core.Contracts.OperationModel.Events;
using SafeExamBrowser.Server.Contracts; using SafeExamBrowser.Server.Contracts.Data;
namespace SafeExamBrowser.Runtime.Operations.Events namespace SafeExamBrowser.Runtime.Operations.Events
{ {

View file

@ -15,6 +15,7 @@ using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts; using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Runtime.Operations.Events; using SafeExamBrowser.Runtime.Operations.Events;
using SafeExamBrowser.Server.Contracts; using SafeExamBrowser.Server.Contracts;
using SafeExamBrowser.Server.Contracts.Data;
using SafeExamBrowser.Settings; using SafeExamBrowser.Settings;
using SafeExamBrowser.SystemComponents.Contracts; using SafeExamBrowser.SystemComponents.Contracts;
@ -53,7 +54,7 @@ namespace SafeExamBrowser.Runtime.Operations
server.Initialize(Context.Next.Settings.Server); 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) if (success)
{ {
@ -69,12 +70,16 @@ namespace SafeExamBrowser.Runtime.Operations
if (success) if (success)
{ {
var info = server.GetConnectionInfo();
var status = TryLoadSettings(uri, UriSource.Server, out _, out var settings); var status = TryLoadSettings(uri, UriSource.Server, out _, out var settings);
fileSystem.Delete(uri.LocalPath); fileSystem.Delete(uri.LocalPath);
if (status == LoadStatus.Success) 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 = settings;
Context.Next.Settings.Browser.StartUrl = exam.Url; Context.Next.Settings.Browser.StartUrl = exam.Url;
result = OperationResult.Success; result = OperationResult.Success;
@ -121,7 +126,7 @@ namespace SafeExamBrowser.Runtime.Operations
public override OperationResult Revert() public override OperationResult Revert()
{ {
var result = OperationResult.Failed; var result = OperationResult.Success;
if (Context.Current?.Settings.SessionMode == SessionMode.Server) if (Context.Current?.Settings.SessionMode == SessionMode.Server)
{ {
@ -139,10 +144,6 @@ namespace SafeExamBrowser.Runtime.Operations
result = OperationResult.Failed; result = OperationResult.Failed;
} }
} }
else
{
result = OperationResult.Success;
}
return result; return result;
} }

View 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; }
}
}

View file

@ -6,7 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
namespace SafeExamBrowser.Server.Contracts namespace SafeExamBrowser.Server.Contracts.Data
{ {
/// <summary> /// <summary>
/// Defines a server exam. /// Defines a server exam.

View file

@ -6,7 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
namespace SafeExamBrowser.Server.Contracts namespace SafeExamBrowser.Server.Contracts.Data
{ {
/// <summary> /// <summary>
/// Defines the result of a communication with a SEB server. /// Defines the result of a communication with a SEB server.

View file

@ -8,6 +8,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using SafeExamBrowser.Server.Contracts.Data;
using SafeExamBrowser.Settings.Server; using SafeExamBrowser.Settings.Server;
namespace SafeExamBrowser.Server.Contracts namespace SafeExamBrowser.Server.Contracts
@ -18,33 +19,42 @@ namespace SafeExamBrowser.Server.Contracts
public interface IServerProxy public interface IServerProxy
{ {
/// <summary> /// <summary>
/// TODO: Return API as well or re-load in proxy instance of client? /// Attempts to initialize a connection to the server.
/// Attempts to initialize a connection to the server. If successful, returns a OAuth2 token as response value.
/// </summary> /// </summary>
ServerResponse<string> Connect(); ServerResponse Connect();
/// <summary> /// <summary>
/// /// TODO
/// </summary> /// </summary>
ServerResponse Disconnect(); ServerResponse Disconnect();
/// <summary> /// <summary>
/// /// Retrieves a list of all currently available exams.
/// </summary> /// </summary>
ServerResponse<IEnumerable<Exam>> GetAvailableExams(); ServerResponse<IEnumerable<Exam>> GetAvailableExams();
/// <summary> /// <summary>
/// /// Retrieves the URI of the configuration file for the given exam.
/// </summary> /// </summary>
ServerResponse<Uri> GetConfigurationFor(Exam exam); ServerResponse<Uri> GetConfigurationFor(Exam exam);
/// <summary>
/// Retrieves the information required to establish a connection with this server.
/// </summary>
ConnectionInfo GetConnectionInfo();
/// <summary> /// <summary>
/// Initializes the server settings to be used for communication. /// Initializes the server settings to be used for communication.
/// </summary> /// </summary>
void Initialize(ServerSettings settings); void Initialize(ServerSettings settings);
/// <summary> /// <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> /// </summary>
ServerResponse SendSessionInfo(string sessionId); ServerResponse SendSessionInfo(string sessionId);
} }

View file

@ -54,10 +54,11 @@
<Reference Include="Microsoft.CSharp" /> <Reference Include="Microsoft.CSharp" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Exam.cs" /> <Compile Include="Data\ConnectionInfo.cs" />
<Compile Include="Data\Exam.cs" />
<Compile Include="IServerProxy.cs" /> <Compile Include="IServerProxy.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ServerResponse.cs" /> <Compile Include="Data\ServerResponse.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\SafeExamBrowser.Settings\SafeExamBrowser.Settings.csproj"> <ProjectReference Include="..\SafeExamBrowser.Settings\SafeExamBrowser.Settings.csproj">

View file

@ -11,13 +11,9 @@ namespace SafeExamBrowser.Server.Data
internal class ApiVersion1 internal class ApiVersion1
{ {
public string AccessTokenEndpoint { get; set; } public string AccessTokenEndpoint { get; set; }
public string HandshakeEndpoint { get; set; } public string HandshakeEndpoint { get; set; }
public string ConfigurationEndpoint { get; set; } public string ConfigurationEndpoint { get; set; }
public string PingEndpoint { get; set; } public string PingEndpoint { get; set; }
public string LogEndpoint { get; set; } public string LogEndpoint { get; set; }
} }
} }

View file

@ -19,6 +19,7 @@ using Newtonsoft.Json.Linq;
using SafeExamBrowser.Configuration.Contracts; using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.Logging.Contracts; using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Server.Contracts; using SafeExamBrowser.Server.Contracts;
using SafeExamBrowser.Server.Contracts.Data;
using SafeExamBrowser.Server.Data; using SafeExamBrowser.Server.Data;
using SafeExamBrowser.Settings.Server; using SafeExamBrowser.Settings.Server;
@ -42,7 +43,7 @@ namespace SafeExamBrowser.Server
this.logger = logger; this.logger = logger;
} }
public ServerResponse<string> Connect() public ServerResponse Connect()
{ {
var success = TryExecute(HttpMethod.Get, settings.ApiUrl, out var response); var success = TryExecute(HttpMethod.Get, settings.ApiUrl, out var response);
var message = ToString(response); var message = ToString(response);
@ -73,7 +74,7 @@ namespace SafeExamBrowser.Server
logger.Error("Failed to load server API!"); logger.Error("Failed to load server API!");
} }
return new ServerResponse<string>(success, oauth2Token, message); return new ServerResponse(success, message);
} }
public ServerResponse Disconnect() public ServerResponse Disconnect()
@ -151,6 +152,16 @@ namespace SafeExamBrowser.Server
return new ServerResponse<Uri>(success, uri, message); 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) public void Initialize(ServerSettings settings)
{ {
this.settings = 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) public ServerResponse SendSessionInfo(string sessionId)
{ {
return new ServerResponse(false, "TODO!"); return new ServerResponse(false, "TODO!");
@ -324,7 +344,7 @@ namespace SafeExamBrowser.Server
} }
catch (TaskCanceledException) 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; break;
} }
catch (Exception e) catch (Exception e)

View file

@ -12,7 +12,7 @@ using SafeExamBrowser.Client.Contracts;
using SafeExamBrowser.Configuration.Contracts; using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.I18n.Contracts; using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts; using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Server.Contracts; using SafeExamBrowser.Server.Contracts.Data;
using SafeExamBrowser.Settings.Browser; using SafeExamBrowser.Settings.Browser;
using SafeExamBrowser.SystemComponents.Contracts.Audio; using SafeExamBrowser.SystemComponents.Contracts.Audio;
using SafeExamBrowser.SystemComponents.Contracts.Keyboard; using SafeExamBrowser.SystemComponents.Contracts.Keyboard;

View file

@ -6,7 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. * 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 namespace SafeExamBrowser.UserInterface.Contracts.Windows.Data
{ {

View file

@ -16,7 +16,7 @@ using SafeExamBrowser.Client.Contracts;
using SafeExamBrowser.Configuration.Contracts; using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.I18n.Contracts; using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts; using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Server.Contracts; using SafeExamBrowser.Server.Contracts.Data;
using SafeExamBrowser.Settings.Browser; using SafeExamBrowser.Settings.Browser;
using SafeExamBrowser.SystemComponents.Contracts.Audio; using SafeExamBrowser.SystemComponents.Contracts.Audio;
using SafeExamBrowser.SystemComponents.Contracts.Keyboard; using SafeExamBrowser.SystemComponents.Contracts.Keyboard;

View file

@ -10,7 +10,7 @@ using System.Collections.Generic;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using SafeExamBrowser.I18n.Contracts; using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Server.Contracts; using SafeExamBrowser.Server.Contracts.Data;
using SafeExamBrowser.UserInterface.Contracts.Windows; using SafeExamBrowser.UserInterface.Contracts.Windows;
using SafeExamBrowser.UserInterface.Contracts.Windows.Data; using SafeExamBrowser.UserInterface.Contracts.Windows.Data;

View file

@ -16,7 +16,7 @@ using SafeExamBrowser.Client.Contracts;
using SafeExamBrowser.Configuration.Contracts; using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.I18n.Contracts; using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts; using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Server.Contracts; using SafeExamBrowser.Server.Contracts.Data;
using SafeExamBrowser.Settings.Browser; using SafeExamBrowser.Settings.Browser;
using SafeExamBrowser.SystemComponents.Contracts.Audio; using SafeExamBrowser.SystemComponents.Contracts.Audio;
using SafeExamBrowser.SystemComponents.Contracts.Keyboard; using SafeExamBrowser.SystemComponents.Contracts.Keyboard;