Implemented basic Pong lobby system

This commit is contained in:
Michael Chen 2022-11-03 12:29:35 +01:00
parent 1254e32749
commit 462e27c88a
Signed by: cnml
GPG Key ID: 5845BF3F82D5F629
24 changed files with 5921 additions and 3 deletions

View File

@ -0,0 +1,7 @@
namespace PongGame;
public enum GameState {
WaitingForPlayers,
InProgress,
Finished,
}

View File

@ -0,0 +1,6 @@
namespace PongGame.Hubs;
public interface IPongClient {
Task GameStateChanged(GameState state);
Task UsernameChanged(string value);
}

61
PongGame/Hubs/PongHub.cs Normal file
View File

@ -0,0 +1,61 @@
using Microsoft.AspNetCore.SignalR;
namespace PongGame.Hubs;
public class PongHub : Hub<IPongClient> {
private const string PLAYER_KEY = "PLAYER";
private readonly PongLobby Lobby;
private readonly ILogger<PongHub> Logger;
public PongHub(PongLobby lobby, ILogger<PongHub> logger) : base() {
Lobby = lobby;
Logger = logger;
}
private PongPlayer Player {
get => (Context.Items.TryGetValue(PLAYER_KEY, out var player) ? player as PongPlayer : null)
?? throw new InvalidProgramException("Player was not assigned at connection start!");
set => Context.Items[PLAYER_KEY] = value;
}
public override Task OnConnectedAsync() {
Player = Lobby.CreatePlayer();
Player.Client = Clients.Client(Context.ConnectionId);
Player.Username = "Anon";
return Task.CompletedTask;
}
private void AssertNotInRoom() {
if (Player.ConnectedRoom is PongRoom currentRoom)
throw new HubException($"User is already connected to room [{currentRoom}]");
}
public Task<string> CreateRoom() {
AssertNotInRoom();
var room = Lobby.CreateRoom(Player);
return Task.FromResult(room.ID);
}
public Task<string> JoinRoom(string roomId) {
AssertNotInRoom();
var room = Lobby.JoinRoom(Player, roomId);
return Task.FromResult(room.ID);
}
public Task LeaveRoom() {
Lobby.LeaveRoom(Player);
return Task.CompletedTask;
}
public Task RequestUsernameChange(string username) {
// TOOD: check this
Logger.LogInformation($"Player {Player} requested username change to [{username}]");
Player.Username = username;
return Task.CompletedTask;
}
public override Task OnDisconnectedAsync(Exception? exception) {
Lobby.RemovePlayer(Player);
return Task.CompletedTask;
}
}

View File

@ -0,0 +1,65 @@
using System.Collections.Concurrent;
using Microsoft.AspNetCore.SignalR;
namespace PongGame.Hubs;
public class PongLobby {
private readonly HashSet<PongPlayer> connectedPlayers = new();
private readonly Dictionary<string, PongRoom> PongRooms = new();
public PongLobby(ILogger<PongLobby> logger)
=> Logger = logger;
public const int ROOM_ID_LENGTH = 4;
public PongPlayer CreatePlayer() {
var player = new PongPlayer();
lock (connectedPlayers)
connectedPlayers.Add(player);
return player;
}
public void RemovePlayer(PongPlayer player) {
if (player.ConnectedRoom is PongRoom room)
room.Leave(player);
lock (connectedPlayers)
_ = connectedPlayers.Remove(player);
}
public PongRoom CreateRoom(PongPlayer player) {
PongRoom room;
lock (PongRooms) {
room = new(GenerateRoomId(), Logger);
PongRooms.Add(room.ID, room);
}
room.Join(player);
return room;
}
public PongRoom JoinRoom(PongPlayer player, string roomId) {
PongRoom? room;
lock (PongRooms) {
room = PongRooms.GetValueOrDefault(roomId);
}
if (room is null) throw new HubException($"Room [{roomId}] not found!");
room.Join(player);
return room;
}
public void LeaveRoom(PongPlayer player) {
if (player.ConnectedRoom is PongRoom room)
room.Leave(player);
}
private readonly Random random = new();
private readonly ILogger<PongLobby> Logger;
private const string ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
private string GenerateRoomId() {
string id;
do {
id = string.Concat(Enumerable.Range(0, ROOM_ID_LENGTH).Select(_ => ALPHABET[random.Next(ALPHABET.Length)]));
} while (PongRooms.ContainsKey(id));
return id;
}
}

View File

@ -0,0 +1,22 @@
using System.Diagnostics;
namespace PongGame.Hubs;
[DebuggerDisplay($"{{{nameof(ToString)}(),nq}}")]
public class PongPlayer {
private string username = default!;
public PongRoom? ConnectedRoom { get; internal set; }
public string Username {
get => username;
internal set {
if (username != value) {
username = value;
Task.Run(() => Client.UsernameChanged(value));
}
}
}
public IPongClient Client { get; internal set; } = default!;
public override string ToString() => $"[{Username}]";
}

65
PongGame/Hubs/PongRoom.cs Normal file
View File

@ -0,0 +1,65 @@
using Microsoft.AspNetCore.SignalR;
namespace PongGame.Hubs;
public class PongRoom {
public string ID { get; }
private readonly ILogger Logger;
public PongRoom(string id, ILogger logger) {
ID = id;
Logger = logger;
}
public PongPlayer? Player1 { get; private set; }
public PongPlayer? Player2 { get; private set; }
public GameState State { get; private set; } = GameState.WaitingForPlayers;
public void Join(PongPlayer player) {
// TODO: synchronize this
if (Player1 is null) {
Player1 = player;
Logger.LogInformation($"[{ID}] {player} joined pong room as player 1!");
} else if (Player2 is null) {
Player2 = player;
Logger.LogInformation($"[{ID}] {player} joined pong room as player 2!");
} else
throw new HubException($"Lobby [{ID}] is already full!");
_ = Task.Run(PlayersChanged);
player.ConnectedRoom = this;
}
public void Leave(PongPlayer player) {
if (Player1 == player) {
Player1 = null;
Logger.LogInformation($"[{ID}] Player 1 {player} left pong room!");
} else if (Player2 == player) {
Player2 = null;
Logger.LogInformation($"[{ID}] Player 2 {player} left pong room!");
}
player.ConnectedRoom = null;
_ = Task.Run(PlayersChanged);
}
private Task PlayersChanged() {
if (Player1 is PongPlayer player1 && Player2 is PongPlayer player2) {
ResumeGame(player1, player2);
} else if (Player1 is null && Player2 is null) {
CloseRoom();
} else
PauseGame();
return Task.CompletedTask;
}
private void ResumeGame(PongPlayer player1, PongPlayer player2) {
Logger.LogInformation($"[{ID}] Pong game started: {player1} vs. {player2}");
State = GameState.InProgress;
player1.Client.GameStateChanged(State);
player2.Client.GameStateChanged(State);
}
private void PauseGame() => State = GameState.WaitingForPlayers;
private void CloseRoom() => State = GameState.Finished;
public override string ToString() => $"[{ID}]";
}

View File

@ -6,4 +6,5 @@
<div class="text-center">
<h1 class="display-4">Welcome</h1>
<a asp-area="" asp-page="/Pong">Pong</a>
</div>

View File

@ -9,7 +9,7 @@ public class IndexModel : PageModel {
_logger = logger;
}
public void OnGet() {
public IActionResult OnGet() {
return RedirectToPage("Pong");
}
}

View File

@ -0,0 +1,27 @@
@page
@model PongModel
@{
ViewData["Title"] = "Pong";
}
<div class="text-center">
<h1 class="display-4">Pong</h1>
<h3 id="connection">Connection Status</h3>
<button id="createlobby" class="btn btn-primary mb-3">Create</button>
<button id="leavelobby" class="btn btn-primary mb-3">Leave</button>
<div class="input-group mb-3">
<input id="roomid" type="text" class="form-control" placeholder="Room ID" aria-label="Room ID" aria-describedby="joinroom">
<button id="joinroom" class="btn btn-outline-secondary" type="button">Button</button>
</div>
<div class="input-group mb-3">
<input id="username" type="text" class="form-control" placeholder="Username" aria-label="Username" aria-describedby="setusername">
<button id="setusername" class="btn btn-outline-secondary" type="button">Set Username</button>
</div>
</div>
<script src="~/js/signalr/signalr.min.js"></script>
<script src="~/js/signalr/signalr-protocol-msgpack.min.js"></script>
<script src="~/js/pong.js"></script>

View File

@ -0,0 +1,15 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace PongGame.Pages;
public class PongModel : PageModel {
private readonly ILogger<PongModel> _logger;
public PongModel(ILogger<PongModel> logger) {
_logger = logger;
}
public void OnGet() {
}
}

View File

@ -8,6 +8,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="6.0.10" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.17.0" />
</ItemGroup>

View File

@ -1,7 +1,14 @@
using MessagePack;
using PongGame.Hubs;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddSingleton<PongLobby>(services
=> new(services.GetRequiredService<ILogger<PongLobby>>()));
builder.Services.AddSignalR()
.AddMessagePackProtocol();
var app = builder.Build();
@ -15,6 +22,9 @@ app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.UseEndpoints(endpoints => {
endpoints.MapRazorPages();
endpoints.MapHub<PongHub>("/pong/hub");
});
app.Run();

20
PongGame/libman.json Normal file
View File

@ -0,0 +1,20 @@
{
"version": "1.0",
"defaultProvider": "jsdelivr",
"libraries": [
{
"library": "@microsoft/signalr-protocol-msgpack@6.0.10",
"destination": "wwwroot/js/"
},
{
"library": "@microsoft/signalr@6.0.10",
"destination": "wwwroot/js/signalr/",
"files": [
"dist/browser/signalr.js",
"dist/browser/signalr.js.map",
"dist/browser/signalr.min.js",
"dist/browser/signalr.min.js.map"
]
}
]
}

View File

@ -0,0 +1,88 @@
"use strict";
console.log("Pong script was run!");
const connection = new signalR.HubConnectionBuilder()
.withUrl("/pong/hub")
.withAutomaticReconnect()
.withHubProtocol(new signalR.protocols.msgpack.MessagePackHubProtocol())
.build();
function getElement(id) {
return document.getElementById(id) ?? console.error(`Element #${id} not found!`)
}
const connectionStatus = getElement("connection");
const createlobby = getElement("createlobby");
const roomid = getElement("roomid");
const joinroom = getElement("joinroom");
const usernameinput = getElement("username");
const setusername = getElement("setusername");
const leavelobby = getElement("leavelobby");
connection.onclose(function (error) {
if (error) {
connectionStatus.textContent = "Unexpected error!";
return console.error(`Connection aborted: ${error.message}`);
}
console.info("Disconnected!");
connectionStatus.textContent = "Closed!";
});
connection.onreconnecting(function (error) {
if (error) {
connectionStatus.textContent = "Reconnecting!";
return console.error(`Connection reconnecting: ${error.message}`);
}
console.info("Reconnecting!");
connectionStatus.textContent = "Reconnecting!";
});
connection.onreconnected(function (connectionId) {
console.info(`Connected as ${connectionId}!`);
connectionStatus.textContent = "Connected!";
});
createlobby.addEventListener("click", function (event) {
connection.invoke("CreateRoom").catch(function (err) {
return console.error(err.toString());
});
event.preventDefault();
});
connection.on("GameStateChanged", function (state) {
console.info(`Game is now in state ${state}`);
});
connection.on("UsernameChanged", function (username) {
console.info(`Username is now ${username}`);
usernameinput.value = username;
});
joinroom.addEventListener("click", function (event) {
connection.invoke("JoinRoom", roomid.value).catch(function (err) {
return console.error(err.toString());
});
event.preventDefault();
});
setusername.addEventListener("click", function (event) {
connection.invoke("RequestUsernameChange", usernameinput.value).catch(function (err) {
return console.error(err.toString());
});
event.preventDefault();
});
leavelobby.addEventListener("click", function (event) {
connection.invoke("LeaveRoom").catch(function (err) {
return console.error(err.toString());
});
event.preventDefault();
});
connection.start().then(function () {
console.info(`Connected!`);
connectionStatus.textContent = "Connected!";
}).catch(function (err) {
connectionStatus.textContent = "Connection failed!";
return console.error(err.toString());
});

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

324
package-lock.json generated Normal file
View File

@ -0,0 +1,324 @@
{
"name": "PongGame",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"dependencies": {
"@microsoft/signalr": "^6.0.10",
"@microsoft/signalr-protocol-msgpack": "^6.0.10"
}
},
"node_modules/@microsoft/signalr": {
"version": "6.0.10",
"resolved": "https://registry.npmjs.org/@microsoft/signalr/-/signalr-6.0.10.tgz",
"integrity": "sha512-ND9LiIYac+ZDgCgW2QzpNfe9BTiOtjc2AX/2GtFIhRGhEzx5CixcNANg2VGj27IAxycAPPnEoy7+QA31Eil7QQ==",
"dependencies": {
"abort-controller": "^3.0.0",
"eventsource": "^1.0.7",
"fetch-cookie": "^0.11.0",
"node-fetch": "^2.6.7",
"ws": "^7.4.5"
}
},
"node_modules/@microsoft/signalr-protocol-msgpack": {
"version": "6.0.10",
"resolved": "https://registry.npmjs.org/@microsoft/signalr-protocol-msgpack/-/signalr-protocol-msgpack-6.0.10.tgz",
"integrity": "sha512-Xj3QuH/HMcLGtc+iZjE8BH/auQVn4FQY9adv7M/wsMpT9TEe1iQJKjD0Hlh+Y42ioNaO0MFVlfFXhkYUK7y5QQ==",
"dependencies": {
"@microsoft/signalr": ">=6.0.10",
"@msgpack/msgpack": "^2.7.0"
}
},
"node_modules/@msgpack/msgpack": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-2.8.0.tgz",
"integrity": "sha512-h9u4u/jiIRKbq25PM+zymTyW6bhTzELvOoUd+AvYriWOAKpLGnIamaET3pnHYoI5iYphAHBI4ayx0MehR+VVPQ==",
"engines": {
"node": ">= 10"
}
},
"node_modules/abort-controller": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
"dependencies": {
"event-target-shim": "^5.0.0"
},
"engines": {
"node": ">=6.5"
}
},
"node_modules/event-target-shim": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
"engines": {
"node": ">=6"
}
},
"node_modules/eventsource": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.2.tgz",
"integrity": "sha512-xAH3zWhgO2/3KIniEKYPr8plNSzlGINOUqYj0m0u7AB81iRw8b/3E73W6AuU+6klLbaSFmZnaETQ2lXPfAydrA==",
"engines": {
"node": ">=0.12.0"
}
},
"node_modules/fetch-cookie": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/fetch-cookie/-/fetch-cookie-0.11.0.tgz",
"integrity": "sha512-BQm7iZLFhMWFy5CZ/162sAGjBfdNWb7a8LEqqnzsHFhxT/X/SVj/z2t2nu3aJvjlbQkrAlTUApplPRjWyH4mhA==",
"dependencies": {
"tough-cookie": "^2.3.3 || ^3.0.1 || ^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/node-fetch": {
"version": "2.6.7",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
"integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/psl": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
"integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag=="
},
"node_modules/punycode": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
"engines": {
"node": ">=6"
}
},
"node_modules/querystringify": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
"integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ=="
},
"node_modules/requires-port": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="
},
"node_modules/tough-cookie": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz",
"integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==",
"dependencies": {
"psl": "^1.1.33",
"punycode": "^2.1.1",
"universalify": "^0.2.0",
"url-parse": "^1.5.3"
},
"engines": {
"node": ">=6"
}
},
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
},
"node_modules/universalify": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
"integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
"engines": {
"node": ">= 4.0.0"
}
},
"node_modules/url-parse": {
"version": "1.5.10",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
"integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
"dependencies": {
"querystringify": "^2.1.1",
"requires-port": "^1.0.0"
}
},
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"node_modules/ws": {
"version": "7.5.9",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz",
"integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==",
"engines": {
"node": ">=8.3.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": "^5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
}
},
"dependencies": {
"@microsoft/signalr": {
"version": "6.0.10",
"resolved": "https://registry.npmjs.org/@microsoft/signalr/-/signalr-6.0.10.tgz",
"integrity": "sha512-ND9LiIYac+ZDgCgW2QzpNfe9BTiOtjc2AX/2GtFIhRGhEzx5CixcNANg2VGj27IAxycAPPnEoy7+QA31Eil7QQ==",
"requires": {
"abort-controller": "^3.0.0",
"eventsource": "^1.0.7",
"fetch-cookie": "^0.11.0",
"node-fetch": "^2.6.7",
"ws": "^7.4.5"
}
},
"@microsoft/signalr-protocol-msgpack": {
"version": "6.0.10",
"resolved": "https://registry.npmjs.org/@microsoft/signalr-protocol-msgpack/-/signalr-protocol-msgpack-6.0.10.tgz",
"integrity": "sha512-Xj3QuH/HMcLGtc+iZjE8BH/auQVn4FQY9adv7M/wsMpT9TEe1iQJKjD0Hlh+Y42ioNaO0MFVlfFXhkYUK7y5QQ==",
"requires": {
"@microsoft/signalr": ">=6.0.10",
"@msgpack/msgpack": "^2.7.0"
}
},
"@msgpack/msgpack": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-2.8.0.tgz",
"integrity": "sha512-h9u4u/jiIRKbq25PM+zymTyW6bhTzELvOoUd+AvYriWOAKpLGnIamaET3pnHYoI5iYphAHBI4ayx0MehR+VVPQ=="
},
"abort-controller": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
"requires": {
"event-target-shim": "^5.0.0"
}
},
"event-target-shim": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="
},
"eventsource": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.2.tgz",
"integrity": "sha512-xAH3zWhgO2/3KIniEKYPr8plNSzlGINOUqYj0m0u7AB81iRw8b/3E73W6AuU+6klLbaSFmZnaETQ2lXPfAydrA=="
},
"fetch-cookie": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/fetch-cookie/-/fetch-cookie-0.11.0.tgz",
"integrity": "sha512-BQm7iZLFhMWFy5CZ/162sAGjBfdNWb7a8LEqqnzsHFhxT/X/SVj/z2t2nu3aJvjlbQkrAlTUApplPRjWyH4mhA==",
"requires": {
"tough-cookie": "^2.3.3 || ^3.0.1 || ^4.0.0"
}
},
"node-fetch": {
"version": "2.6.7",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
"integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
"requires": {
"whatwg-url": "^5.0.0"
}
},
"psl": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
"integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag=="
},
"punycode": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
},
"querystringify": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
"integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ=="
},
"requires-port": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="
},
"tough-cookie": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz",
"integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==",
"requires": {
"psl": "^1.1.33",
"punycode": "^2.1.1",
"universalify": "^0.2.0",
"url-parse": "^1.5.3"
}
},
"tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
},
"universalify": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
"integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg=="
},
"url-parse": {
"version": "1.5.10",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
"integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
"requires": {
"querystringify": "^2.1.1",
"requires-port": "^1.0.0"
}
},
"webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
},
"whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"requires": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"ws": {
"version": "7.5.9",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz",
"integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==",
"requires": {}
}
}
}

6
package.json Normal file
View File

@ -0,0 +1,6 @@
{
"dependencies": {
"@microsoft/signalr": "^6.0.10",
"@microsoft/signalr-protocol-msgpack": "^6.0.10"
}
}