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 = ?`, [