From f85bd3fcb8ff638f1d51ead7944c042e63faf77e Mon Sep 17 00:00:00 2001 From: Holly Date: Fri, 26 Apr 2024 03:01:06 +0100 Subject: [PATCH] parties work! --- client/Terminal-00-Multiuser.user.js | 212 +++++++++++---------------- server/config.example.json | 2 +- server/index.ts | 56 ++++++- server/objects/RemoteUser.ts | 8 +- server/repos/UserRepo.ts | 4 +- 5 files changed, 148 insertions(+), 134 deletions(-) diff --git a/client/Terminal-00-Multiuser.user.js b/client/Terminal-00-Multiuser.user.js index 6f13129..6cee498 100644 --- a/client/Terminal-00-Multiuser.user.js +++ b/client/Terminal-00-Multiuser.user.js @@ -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

"; + 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(); - }*/ })(); \ No newline at end of file diff --git a/server/config.example.json b/server/config.example.json index 811a55e..51124d0 100644 --- a/server/config.example.json +++ b/server/config.example.json @@ -1,6 +1,6 @@ { "ports": { - "http": 38194, + "http": 39194, "ws": 39195 }, "session": { diff --git a/server/index.ts b/server/index.ts index 7090975..a31117b 100644 --- a/server/index.ts +++ b/server/index.ts @@ -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(); + 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: { diff --git a/server/objects/RemoteUser.ts b/server/objects/RemoteUser.ts index cce9dac..c6153bf 100644 --- a/server/objects/RemoteUser.ts +++ b/server/objects/RemoteUser.ts @@ -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) { diff --git a/server/repos/UserRepo.ts b/server/repos/UserRepo.ts index 4a9de68..da448ca 100644 --- a/server/repos/UserRepo.ts +++ b/server/repos/UserRepo.ts @@ -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 = ?`, [