using System; using System.Net.WebSockets; using BubbleSocketCore.Handlers; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.IdentityModel.Tokens; using System.Text; using System.IO; using Microsoft.Extensions.FileProviders; using Microsoft.AspNetCore.Cors.Infrastructure; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Hosting; using System.Threading; using BubbleSocketCore.Simulation; namespace BubbleSocketCore { public class Startup { public IConfiguration Configuration { get; } public CancellationTokenSource serverShutdown; public Startup(IConfiguration configuration) { Configuration = configuration; serverShutdown = new CancellationTokenSource(); } public void ConfigureServices(IServiceCollection services) { services.AddHostedService(); services.AddControllers(); services.AddAuthentication(options => { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; }).AddJwtBearer(options => { options.RequireHttpsMetadata = false; options.SaveToken = true; options.TokenValidationParameters = new TokenValidationParameters() { ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JwtSettings:SecretKey"])), ValidateIssuer = true, ValidIssuer = Configuration["JwtSettings:Issuer"], ValidateAudience = true, ValidAudience = Configuration["JwtSettings:Audience"], ValidateLifetime = true, ClockSkew = TimeSpan.FromMinutes(Configuration.GetValue("JwtSettings:ExpirationInHours")) }; }); services.AddMvc(); services.AddCors(options => { options.AddPolicy(name: "bubblesocket_corspolicy", policy => { policy.AllowAnyOrigin(); policy.AllowAnyMethod(); policy.AllowAnyHeader(); }); }); } public void Configure( IApplicationBuilder app, IWebHostEnvironment env, ILogger logger, ICorsService corsService, ICorsPolicyProvider corsPolicyProvider ) { string errorPath = Path.Combine(Directory.GetCurrentDirectory(), Configuration.GetValue("RelativeErrorFilePath")); logger.LogInformation($"Loading static error pages from {errorPath}"); app.UseStatusCodePages(new StatusCodePagesOptions() { HandleAsync = async (context) => { var filePath = Path.Combine(errorPath, context.HttpContext.Response.StatusCode + ".html"); var responseMessage = $"Error {context.HttpContext.Response.StatusCode}"; if (System.IO.File.Exists(filePath)) { responseMessage = System.IO.File.ReadAllText(filePath); } await context.HttpContext.Response.WriteAsync(responseMessage); } }); string staticPath = Path.Combine(Directory.GetCurrentDirectory(), Configuration.GetValue("RelativeStaticFilePath")); logger.LogInformation($"Loading static files from {staticPath}"); PhysicalFileProvider staticFileProvider = new PhysicalFileProvider(staticPath); app.UseDefaultFiles(new DefaultFilesOptions() { FileProvider = staticFileProvider, DefaultFileNames = new string[] { "index.html" } }); app.UseStaticFiles(new StaticFileOptions() { FileProvider = staticFileProvider, ServeUnknownFileTypes = true, OnPrepareResponse = (ctx) => { var policy = corsPolicyProvider.GetPolicyAsync(ctx.Context, "bubblesocket_corspolicy") .ConfigureAwait(false) .GetAwaiter().GetResult(); var corsResult = corsService.EvaluatePolicy(ctx.Context, policy); corsService.ApplyResult(corsResult, ctx.Context.Response); } }); app.UseRouting(); app.UseCors("bubblesocket_corspolicy"); app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); app.UseWebSockets(new WebSocketOptions() { KeepAliveInterval = TimeSpan.FromSeconds(120) }); app.Use(async (context, next) => { try { IHandler handler = new HandlerFactory(Configuration, app.ApplicationServices.GetService()).Get(context.Request); if (context.WebSockets.IsWebSocketRequest) { using (WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync()) { handler.AddSocket(webSocket); try { await handler.Listen(context, webSocket, serverShutdown.Token); } catch (WebSocketException) { //unhandled socket exception! } } } else { await next(); } } catch (HandlerNotFoundException) { // await webSocket.CloseOutputAsync(WebSocketCloseStatus.EndpointUnavailable, $"Websocket protocol not found: {e.Message}", CancellationToken.None); } catch (Exception e) { logger.LogError($"Unexpected Error: {e.Message}"); } }); app.ApplicationServices.GetRequiredService().ApplicationStopping.Register(() => { serverShutdown.Cancel(); }); } //frame //frame id (int counting up? deterministic uuid?) //ms it started //inputs pressed //each frame is 16ms (Time % 16) //need synchronize step for setInterval //last frame id seen //simulation //keeps position of all entities each frame for 10 seconds (160 frames stored) //if player gives a frameid that's too old, player gets force-updated simulation //sort list of inputs based on ms & adjust game state //game //3 frame input delay (48ms) //maybe determine based on latency of players in sector? } }