Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement RealtimeCodeEditor #146

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
73 changes: 73 additions & 0 deletions samples/RealtimeCodeEditor/Controllers/HomeController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using RealtimeCodeEditor.Models;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;

namespace RealtimeCodeEditor.Controllers
{
[Controller]
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
private readonly SessionHandler _sessionHandler;

public HomeController(ILogger<HomeController> logger, SessionHandler sessionHandler)
{
_logger = logger;
_sessionHandler = sessionHandler;
}

[AllowAnonymous]
public IActionResult Index()
{
return View();
}

[AllowAnonymous]
public IActionResult Privacy()
{
return View();
}

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}

[HttpGet]
public IActionResult Session(string code)
{
string requestSourceUser = User.Claims.Where(claim => claim.Type == "preferred_username").First().Value;
if (_sessionHandler.IsLegalUser(code, requestSourceUser))
{
_sessionHandler.JoinSession(code, requestSourceUser);
ViewBag.SessionModel = _sessionHandler.GenerateSessionModel(code, requestSourceUser);
return View("~/Views/Home/CodeEditor.cshtml");
}
return Redirect("/");
}

[HttpPost]
public IActionResult StartNewSession(string user)
{
_logger.LogInformation("StartNewSession user: {0}", user);
string sessionCode = _sessionHandler.CreateSession(user);
return Redirect("/Home/Session?code=" + sessionCode);
}

[HttpPost]
public IActionResult EnterSession(string user, string sessionCode)
{
_logger.LogInformation("EnterSession user: {0}, code: {1}", user, sessionCode);
return Redirect("/Home/Session?code=" + sessionCode);
}
}
}
89 changes: 89 additions & 0 deletions samples/RealtimeCodeEditor/Hubs/CodeEditorHub.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Logging;
using RealtimeCodeEditor.Models;
using RealtimeCodeEditor.Models.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;

namespace RealtimeCodeEditor.Hubs
{
public class CodeEditorHub : Hub
{
private readonly ILogger<CodeEditorHub> _logger;
private readonly SessionHandler _sessionHandler;

public CodeEditorHub(ILogger<CodeEditorHub> logger, SessionHandler sessionHandler)
{
_logger = logger;
_sessionHandler = sessionHandler;
}

private bool CheckSessionState(string sessionCode, string user)
{
if (!_sessionHandler.IsLegalUser(sessionCode, user)) {
Clients.Client(Context.ConnectionId).SendAsync("expireSession");
return false;
}

return true;
}

public async Task OnEnterSession(string sessionCode, string user)
{
_logger.LogInformation(Context.User.Identity.Name);
if (CheckSessionState(sessionCode, user)) {
_logger.LogInformation("OnEnterSession code: {0}, user: {1}", sessionCode, user);

await Groups.AddToGroupAsync(Context.ConnectionId, sessionCode);

await Clients.Client(Context.ConnectionId).SendAsync("enableEditor");
}
}

public async Task OnCodeEditorStateChanged(string sessionCode, string user, string content)
{
if (CheckSessionState(sessionCode, user))
{
_logger.LogInformation("OnCodeEditorStateChanged");
_sessionHandler.UpdateSessionState(sessionCode, content);

await Clients.GroupExcept(sessionCode, Context.ConnectionId).SendAsync("updateCodeEditor", content);
}
}

public async Task OnCodeEditorLocked(string sessionCode, string user)
{
if (CheckSessionState(sessionCode, user))
{
if (!_sessionHandler.IsLegalCreator(sessionCode, user))
{
return;
}

_logger.LogInformation("OnCodeEditorLocked");
_sessionHandler.LockSession(sessionCode);

await Clients.Group(sessionCode).SendAsync("lockCodeEditor");
}
}

public async Task OnCodeEditorUnlocked(string sessionCode, string user)
{
if (CheckSessionState(sessionCode, user))
{
if (!_sessionHandler.IsLegalCreator(sessionCode, user))
{
return;
}

_logger.LogInformation("OnCodeEditorUnlocked");
_sessionHandler.UnlockSession(sessionCode);

await Clients.Group(sessionCode).SendAsync("unlockCodeEditor");
}
}
}
}
102 changes: 102 additions & 0 deletions samples/RealtimeCodeEditor/Models/Entities/Session.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace RealtimeCodeEditor.Models.Entities
{
public class Session : IDisposable
{
public string SessionCode { get; private set; }

public SessionTypeEnum Type { get; set; }

public string Creator { get; private set; }

public bool IsLocked { get; private set; }

public string SavedState { get; set; }

public DateTime LastActiveDateTime { get; set; }

private ISet<string> _users;

public Session(string sessionCode, string creator)
{
SessionCode = sessionCode;
Type = SessionTypeEnum.Active;
Creator = creator;
IsLocked = false;
_users = new HashSet<string>();
}

public void AddUser(string user)
{
if (Type == SessionTypeEnum.Expired)
{
throw new Exception("Attempts to update an expired session.");
}

_users.Add(user);
}

public void RemoveUser(string user)
{
if (Type == SessionTypeEnum.Expired)
{
throw new Exception("Attempts to update an expired session.");
}

_users.Remove(user);
}

public string[] GetUsers(string except = "")
{
if (except == "")
{
return _users.ToArray();
}
else
{
return _users.Where(s => s != except).ToArray();
}
}

public bool HasUser(string user)
{
return _users.Contains<string>(user);
}

public void TouchSession()
{
if (Type == SessionTypeEnum.Expired)
{
throw new Exception("Attempts to touch an expired session.");
}

LastActiveDateTime = DateTime.UtcNow;
}

public void Expire()
{
Type = SessionTypeEnum.Expired;
}

public void Lock()
{
IsLocked = true;
}

public void Unlock()
{
IsLocked = false;
}

public void Dispose()
{
_users.Clear();
}

}
}
12 changes: 12 additions & 0 deletions samples/RealtimeCodeEditor/Models/Entities/SessionTypeEnum.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace RealtimeCodeEditor.Models.Entities
{
public enum SessionTypeEnum
{
Active, Expired,
}
}
11 changes: 11 additions & 0 deletions samples/RealtimeCodeEditor/Models/ErrorViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System;

namespace RealtimeCodeEditor.Models
{
public class ErrorViewModel
{
public string RequestId { get; set; }

public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
}
}
Loading