parties work!

This commit is contained in:
Holly Stubbs 2024-04-26 03:01:06 +01:00
parent b0178cb82d
commit f85bd3fcb8
Signed by: tgpholly
GPG key ID: B8583C4B7D18119E
5 changed files with 148 additions and 134 deletions

View file

@ -1,7 +1,7 @@
// ==UserScript==
// @name Terminal 00 Multiuser
// @namespace https://*.angusnicneven.com/*
// @version 20240420.2
// @version 20240426.1
// @description Probe with friends!
// @author tgpholly
// @match https://*.angusnicneven.com/*
@ -35,6 +35,8 @@ if (!window.TE_ACTIVE) {
(function() {
'use strict';
const USERSCRIPT_VERSION = "20240426.1";
if (!continueRunningScript) {
return;
}
@ -182,9 +184,21 @@ kbd {
white-space: nowrap;
}
.mplink, .mplink:visited {
color: #d2738a;
}
.mplink:hover {
color: #c1b492;
}
`.split("\n").join("").split("\r").join("").split("\t").join("");
document.head.appendChild(styles);
if (!localStorage["mpconnectonload"]) {
localStorage["mpconnectonload"] = true;
}
const otherCursors = document.createElement("div");
otherCursors.id = "otherCursors";
document.body.appendChild(otherCursors);
@ -220,12 +234,12 @@ kbd {
buttonBox.classList.add("buttons");
user.appendChild(buttonBox);
const followButton = document.createElement("button");
/*const followButton = document.createElement("button");
followButton.innerText = "F";
buttonBox.appendChild(followButton);
buttonBox.appendChild(followButton);*/
const gotoButton = document.createElement("button");
gotoButton.innerText = "G";
gotoButton.innerText = "Go To";
gotoButton.onclick = () => window.location.href = location;
buttonBox.appendChild(gotoButton);
@ -241,6 +255,15 @@ kbd {
otherCursors.style = `width:${clientWidth = document.body.getBoundingClientRect().width}px;height:${window.innerHeight}px`;
}
}, 1000);
/*fetch(`https://git.eusv.net/tgpholly/t00-multiuser/raw/branch/master/client/Terminal-00-Multiuser.user.js?${Date.now()}`).then(res => {
res.text(text => {
if (text.includes("@version")) {
const version = file.split("@version")[1].split("\n")[0].trim().split(".").join("");
if ()
}
});
});*/
const keepAlivePacket = createWriter(Endian.LE, 1).writeByte(MessageType.KeepAlive).toBuffer();
@ -472,97 +495,6 @@ kbd {
break;
}
case MessageType.ClientLeft:
{
const clientId = reader.readUInt();
removeClient(clientId);
}
case MessageType.Ping:
{
const cursorX = reader.readFloat();
const cursorY = reader.readInt();
createPing(cursorX * clientWidth, cursorY);
}
}
}
ws.onmessage = (e) => {
e.data.arrayBuffer().then(onMessage);
}
function onCloseAndError() {
if (keepAliveInterval) {
clearInterval(keepAliveInterval);
keepAliveInterval = undefined;
}
ws = undefined;
ready = false;
setTimeout(() => doConnect(localStorage["mpapikey"]), 5000);
}
ws.onclose = onCloseAndError;
ws.onerror = onCloseAndError;
}
function doConnect(apiKey) {
const Buffer = getBufferClass();
ws = new WebSocket(window.location.href.includes("//localhost:") ? "ws://localhost:39195" : "wss://ws.eusv.net/t00mp");
let keepAliveInterval;
ws.onopen = () => {
otherCursors.innerHTML = "";
selfCursor = new RemoteClient(localStorage["t00mp_username"]);
selfCursor.probeImage.style.visibility = "hidden";
selfCursor.element.style.visibility = localStorage["t00mp_cursorStyle"] ?? "hidden";
selfCursor.hasBeenMoved = true;
const currentPage = window.location.href.split("/").slice(3).join("/");
ws.send(createWriter(Endian.LE, 4 + apiKey.length + currentPage.length)
.writeByte(MessageType.ClientDetails)
.writeShortString(apiKey)
.writeString(currentPage)
.toBuffer());
keepAliveInterval = setInterval(() => {
ws.send(keepAlivePacket);
}, 5000);
}
function onMessage(buf) {
const reader = createReader(Endian.LE, Buffer.from(buf));
switch (reader.readByte()) {
case MessageType.Clients:
{
const clientCount = reader.readUShort();
for (let i = 0; i < clientCount; i++) {
const clientId = reader.readUInt();
const clientName = reader.readShortString();
const clientX = reader.readFloat();
const clientY = reader.readInt();
remoteClients.set(clientId, new RemoteClient(clientName)).rawSetPosInit(clientX, clientY);
}
ready = true;
if (windowContainer) {
windowContainer.remove();
windowContainer = null;
}
createFirstTimeDialog();
break;
}
case MessageType.ClientJoined:
{
const clientId = reader.readUInt();
const clientName = reader.readShortString();
remoteClients.set(clientId, new RemoteClient(clientName));
break;
}
case MessageType.CursorPos:
{
const clientId = reader.readUInt();
if (remoteClients.has(clientId)) {
const cursorX = reader.readFloat();
const cursorY = reader.readInt();
remoteClients.get(clientId).rawSetPos(cursorX, cursorY);
}
break;
}
case MessageType.ClientLeft:
{
const clientId = reader.readUInt();
removeClient(clientId);
@ -577,6 +509,15 @@ kbd {
}
case MessageType.GroupData:
{
groupUIBase.style = "";
groupUsers.innerHTML = "";
groupTitle.innerText = reader.readShortString();
const groupUserCount = reader.readUShort();
for (let i = 0; i < groupUserCount; i++) {
const groupUsername = reader.readShortString();
const groupUserLocation = reader.readString();
createGroupUser(groupUsername, groupUserLocation);
}
break;
}
}
@ -618,6 +559,44 @@ kbd {
const buttons = document.createElement("div");
buttons.style.marginTop = "1rem";
const disconnectButton = document.createElement("button");
disconnectButton.innerText = localStorage["mpconnectonload"] === "true" ? "Disconnect" : "Connect";
disconnectButton.onclick = () => {
if (localStorage["mpconnectonload"] === "true") {
localStorage["mpconnectonload"] = false;
if (ws) {
ws.close();
}
} else {
localStorage["mpconnectonload"] = true;
doConnect(localStorage["mpapikey"]);
}
disconnectButton.innerText = localStorage["mpconnectonload"] === "true" ? "Disconnect" : "Connect";
};
buttons.appendChild(disconnectButton);
const manageAccountLink = document.createElement("a");
manageAccountLink.style.display = "none";
manageAccountLink.href = "https://multiprobe.eusv.net/";
manageAccountLink.target = "_blank";
buttons.appendChild(manageAccountLink);
const manageAccount = document.createElement("button");
manageAccount.style.marginLeft = "1rem";
manageAccount.innerText = "Manage Account";
manageAccount.onclick = () => {
manageAccountLink.click();
}
buttons.appendChild(manageAccount);
const closeButton = document.createElement("button");
closeButton.innerText = "Close";
closeButton.onclick = () => {
bg.remove();
windowContainer = null;
};
closeButton.style.marginLeft = "1rem";
buttons.appendChild(closeButton);
dialog.appendChild(buttons);
document.body.appendChild(bg);
@ -677,22 +656,15 @@ kbd {
bg.appendChild(dialog);
const title = document.createElement("h4");
title.innerText = "MultiProbe";
title.style.marginBottom = ".5rem";
dialog.appendChild(title);
/*const submitFunction = (event) => {
// Jank
if (event && event.keyCode !== 13) {
return;
}
if (username.value.trim() === "") {
alert("Username must be valid");
return;
}
localStorage["t00mp_username"] = username.value;
bg.remove();
window.location.href = window.location.href;
};*/
const manageAccountLink = document.createElement("a");
manageAccountLink.className = "mplink";
manageAccountLink.href = "https://multiprobe.eusv.net/";
manageAccountLink.innerHTML = "Click here to create an account<br><br>";
manageAccountLink.target = "_blank";
manageAccountLink.style.marginLeft = manageAccountLink.style.marginRight = ".5rem";
dialog.appendChild(manageAccountLink);
const loginForm = document.createElement("form");
dialog.appendChild(loginForm);
const username = document.createElement("input");
@ -700,7 +672,6 @@ kbd {
username.maxLength = 32;
username.style.width = "12rem";
username.name = "username";
//username.onkeypress = submitFunction;
loginForm.appendChild(username);
const password = document.createElement("input");
password.type = "password";
@ -715,18 +686,7 @@ kbd {
const submitButton = document.createElement("button");
submitButton.innerText = "Connect";
submitButton.type = "submit";
//submitButton.onclick = () => submitFunction(null);
buttons.appendChild(submitButton);
/*if (localStorage["t00mp_username"] !== "") {
const disconnectButton = document.createElement("button");
disconnectButton.innerText = "Disconnect";
disconnectButton.onclick = () => {
localStorage["t00mp_username"] = "";
bg.remove();
window.location.href = window.location.href;
}
buttons.appendChild(disconnectButton);
}*/
const doNotButton = document.createElement("button");
doNotButton.innerText = "Close";
doNotButton.onclick = () => bg.remove();
@ -779,7 +739,7 @@ kbd {
openMenuButton.style = "opacity:0.25;position:fixed;top:0px;right:0px;z-index:9999999;margin:4px;background-color:black;color:white;border:1px solid white";
openMenuButton.innerText = "MultiProbe Menu";
openMenuButton.onclick = () => {
if (ws) {
if (ws || localStorage["mpapikey"]) {
createOnlineDialog();
} else {
createLoginDialog();
@ -787,11 +747,7 @@ kbd {
};
document.body.appendChild(openMenuButton);
if (localStorage["mpapikey"] && localStorage["mpapikey"] !== "") {
if (localStorage["mpapikey"] && localStorage["mpapikey"] !== "" && localStorage["mpconnectonload"] === "true") {
doConnect(localStorage["mpapikey"]);
}
/*if (username !== "") {
doConnect();
}*/
})();

View file

@ -1,6 +1,6 @@
{
"ports": {
"http": 38194,
"http": 39194,
"ws": 39195
},
"session": {

View file

@ -18,8 +18,8 @@ import SessionUser from "./objects/SessionUser";
import PasswordUtility from "./utilities/PasswordUtility";
import CreateEditPartyData from "./interfaces/CreateEditPartyData";
import JoinPartyData from "./interfaces/JoinPartyData";
import UserParty from "./objects/UserParty";
import IdData from "./interfaces/IdData";
import Party from "./objects/Party";
Console.customHeader(`MultiProbe server started at ${new Date()}`);
@ -325,6 +325,14 @@ function sendToAll(user:RemoteUser, data:Buffer) {
});
}
function sendToAllInGroup(user:RemoteUser, data:Buffer) {
users.forEach(otherUser => {
if (otherUser.groupId === user.groupId && otherUser.userId !== user.userId) {
otherUser.send(data);
}
});
}
websocketServer.on("connection", (socket) => {
const myUUID = crypto.randomUUID();
let user:RemoteUser;
@ -335,9 +343,43 @@ websocketServer.on("connection", (socket) => {
const userLeftPacket = createWriter(Endian.LE, 5).writeByte(MessageType.ClientLeft).writeUInt(user.id).toBuffer();
users.forEach(otherUser => otherUser.send(userLeftPacket));
sendGroupUpdate(user);
}
}
async function sendGroupUpdate(sendUser:RemoteUser, groupSend = false) {
if (!sendUser || user.groupId === Number.MIN_VALUE) {
return;
}
const usersInGroup = new FunkyArray<number, RemoteUser>();
let totalUsernameLength = 0;
await users.forEach(otherUser => {
if (sendUser.groupId === otherUser.groupId && sendUser.userId !== otherUser.userId) {
if (usersInGroup.has(otherUser.userId)) {
totalUsernameLength += otherUser.username.length;
}
usersInGroup.set(otherUser.userId, otherUser);
}
if (!groupSend && sendUser.userId !== otherUser.userId) {
sendGroupUpdate(otherUser, true);
}
});
const writer = createWriter(Endian.LE)
.writeByte(MessageType.GroupData)
.writeShortString(sendUser.groupName)
.writeUShort(usersInGroup.length);
await usersInGroup.forEach(otherUser => {
writer.writeShortString(otherUser.username).writeString(otherUser.rawURL);
});
const groupData = writer.toBuffer();
socket.send(groupData);
}
socket.on("close", closeOrError);
socket.on("error", closeOrError);
@ -356,6 +398,11 @@ websocketServer.on("connection", (socket) => {
if (dbUser == null) {
return;
}
const dbUserParty = await UserService.GetActiveParty(dbUser.Id);
let dbParty: Party | null = null;
if (dbUserParty) {
dbParty = await UserService.GetParty(dbUserParty.PartyId);
}
const rawURL = reader.readString();
let page = rawURL.toLowerCase().replace(".htm", "").replace(".html", "");
@ -374,9 +421,14 @@ websocketServer.on("connection", (socket) => {
for (const otherUser of usersOnPage) {
usersToSend.writeUInt(otherUser.id).writeShortString(otherUser.username).writeFloat(otherUser.cursorX).writeInt(otherUser.cursorY);
}
user = users.set(myUUID, new RemoteUser(socket, dbUser.Username, page, rawURL));
if (dbParty) {
user = users.set(myUUID, new RemoteUser(socket, dbUser.Username, page, rawURL, dbUser.Id, dbParty.Id, dbParty.Name));
} else {
user = users.set(myUUID, new RemoteUser(socket, dbUser.Username, page, rawURL, dbUser.Id, Number.MIN_VALUE, ""));
}
sendToAllButSelf(user, createWriter(Endian.LE, 6 + dbUser.Username.length).writeByte(MessageType.ClientJoined).writeUInt(user.id).writeShortString(dbUser.Username).toBuffer());
user.send(usersToSend.toBuffer());
sendGroupUpdate(user);
break;
case MessageType.CursorPos:
{

View file

@ -12,8 +12,11 @@ export default class RemoteUser {
public cursorY:number = 0;
public allowedPings:number;
public lastPingReset:number;
public userId:number;
public groupId:number;
public groupName:string;
constructor(socket:WebSocket, username:string, currentURL:string, rawURL:string) {
constructor(socket:WebSocket, username:string, currentURL:string, rawURL:string, userId:number, groupId:number, groupName:string) {
this.socket = socket;
this.id = RemoteUser.USER_IDS++;
this.username = username;
@ -21,6 +24,9 @@ export default class RemoteUser {
this.rawURL = rawURL;
this.allowedPings = 10;
this.lastPingReset = Date.now();
this.userId = userId;
this.groupId = groupId;
this.groupName = groupName;
}
send(data:Buffer) {

View file

@ -38,8 +38,8 @@ export default class UserRepo {
public static async insertUpdate(user:User) {
if (user.Id === Number.MIN_VALUE) {
await Database.Instance.query("INSERT User (Username, PasswordHash, PasswordSalt, APIKey, CreatedByUserId, CreatedDatetime, LastModifiedByUserId, LastModifiedDatetime, DeletedByUserId, DeletedDatetime, IsDeleted) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [
user.Username, user.PasswordHash, user.PasswordSalt, user.APIKey, user.CreatedByUserId, user.CreatedDatetime.getTime(), user.LastModifiedByUserId ?? null, user.LastModifiedDatetime?.getTime() ?? null, user.DeletedByUserId ?? null, user.DeletedDatetime?.getTime() ?? null, Number(user.IsDeleted)
await Database.Instance.query("INSERT User (Username, PasswordHash, PasswordSalt, CreatedByUserId, CreatedDatetime, LastModifiedByUserId, LastModifiedDatetime, DeletedByUserId, DeletedDatetime, IsDeleted) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [
user.Username, user.PasswordHash, user.PasswordSalt, user.CreatedByUserId, user.CreatedDatetime.getTime(), user.LastModifiedByUserId ?? null, user.LastModifiedDatetime?.getTime() ?? null, user.DeletedByUserId ?? null, user.DeletedDatetime?.getTime() ?? null, Number(user.IsDeleted)
]);
} else {
await Database.Instance.query(`UPDATE User SET Username = ?, PasswordHash = ?, PasswordSalt = ?, APIKey = ?, CreatedByUserId = ?, CreatedDatetime = ?, LastModifiedByUserId = ?, LastModifiedDatetime = ?, DeletedByUserId = ?, DeletedDatetime = ?, IsDeleted = ?, WHERE Id = ?`, [