diff --git a/client.js b/client.js index 1940761..b55e0e3 100644 --- a/client.js +++ b/client.js @@ -1,12 +1,42 @@ -const channelSocket = new WebSocket("ws://localhost:19180/connect"); const __messengerjs__ = { "sessionID": null, "imTheChannelOwner": false, "myNickname": "", "onloadfunction": null, "onloadargs": null, - "fade": null + "fade": null, + "channelSocket": null, + "getWSAddress": null, + "callIfExists": null, + "beforeUnloadHandler": null, + "ParseCommandString": null, + "EncodeCommandString": null, + "changeUsernameDialog": null } + +__messengerjs__.getWSAddress = function() { + const ws = "ws://localhost:19180/connect"; + const wss = "wss://localhost:19543/connect"; + + if(window.location.host === "localhost" || window.location.protocol === "file:") { + // local users probably want ws + return ws; + } + + if(window.hasOwnProperty("isSecureContext")) { + if(window.isSecureContext) { + // use wss + return wss; + } else { + // use ws + return ws; + } + } else { + // use wss (can't be sure) + return wss; + } +} +__messengerjs__.channelSocket = new WebSocket(__messengerjs__.getWSAddress()); __messengerjs__.fade = document.createElement("div"); __messengerjs__.fade.style.cssText = "position: absolute;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.75);color:white;text-align:center;font-family:sans-serif;"; __messengerjs__.fade.textContent = "connecting to WebSocket server..."; @@ -17,14 +47,14 @@ __messengerjs__.callIfExists = function(fnName) { } } -const beforeUnloadHandler = function(ev) { +__messengerjs__.beforeUnloadHandler = function(ev) { Channel_OnAppClose(); - channelSocket.send(new TextEncoder("utf8").encode(EncodeCommandString(["disconnect"]))); - channelSocket.close(1000, "unloading"); + __messengerjs__.channelSocket.send(new TextEncoder("utf8").encode(__messengerjs__.EncodeCommandString(["disconnect"]))); + __messengerjs__.channelSocket.close(1000, "unloading"); }; /** @section guacamole **/ -const ParseCommandString = function(instruction) { +__messengerjs__.ParseCommandString = function(instruction) { if(typeof instruction !== "string") { return; } @@ -38,7 +68,11 @@ const ParseCommandString = function(instruction) { break; } - position = (parseInt(instruction.slice(position + 1, length)) + length) + 1; // todo: not overflow + position = (parseInt(instruction.slice(position + 1, length)) + length) + 1; + if(position > instruction.length) { + return; + } + sections.push(instruction.slice(length + 1, position) .replace(/'/g, "'") .replace(/"/g, '"') @@ -54,7 +88,7 @@ const ParseCommandString = function(instruction) { } return sections; } -const EncodeCommandString = function(sections) { +__messengerjs__.EncodeCommandString = function(sections) { if(Array.isArray(sections) === false) { return; } @@ -62,15 +96,19 @@ const EncodeCommandString = function(sections) { const argv = sections; for (let argc = 0; argc < sections.length ; argc++) { if(typeof sections[argc] !== "string") { - argv[argc] = sections[argc].toString(); + if(sections[argc] === null || typeof sections[argc] === "undefined") { + argv[argc] = ""; + } else { + argv[argc] = sections[argc].toString(); + } } argv[argc] = argv[argc] - .replace(/'/g, "'") - .replace(/"/g, '"') - .replace(/\//g, '/' ) - .replace(//g, '>' ) .replace(/&/g, '&' ) + .replace(/>/g, '>' ) + .replace(/, Incoming: bool, Status: num<0-3>} -window.external.Channel.SendData = function(d) {channelSocket.send(d)}; // this is dumb but trying to redirect this gives you TypeError's +window.external.Channel.SendData = function(d) { + try { + __messengerjs__.channelSocket.send(d) + } catch(e) { + window.external.Channel.Error.Data = d; + window.external.Channel.Error.Type = 0xBAD50CE7|0; // bad socket, might change this to something else + __messengerjs__.callIfExists("Channel_OnDataError"); + __messengerjs__.callIfExists("Channel_OnTypeChanged"); + if(__messengerjs__.channelSocket.readyState !== 1) { + __messengerjs__.fade.textContent = "the connection was closed while trying to send data." + __messengerjs__.fade.style.display = "block"; + } + } +}; // this is dumb but trying to redirect this gives you TypeError's window.external.Channel.Initialize = function() { // no idea we're already pretty initalized by the time most things call __messengerjs__.callIfExists("Channel_OnTypeChanged"); // make client check if its even connected still }; Object.defineProperty(window.external.Channel, "Type", { get: function Type() { - if(channelSocket.readyState === 1) { + if(__messengerjs__.channelSocket.readyState === 1) { return 1; // Indirect; } else { return 2; // Disconnected; @@ -139,8 +190,8 @@ Object.defineProperty(window.external.Users, "me", { so why would i even bother? */ /** websocket stuff **/ -channelSocket.binaryType = "arraybuffer"; // the alternative is blob.arrayBuffer(); IT'S A PROMISE CALL AND PROMISES SUXX -channelSocket.onmessage = function(event) { +__messengerjs__.channelSocket.binaryType = "arraybuffer"; // the alternative is blob.arrayBuffer(); IT'S A PROMISE CALL AND PROMISES SUXX +__messengerjs__.channelSocket.onmessage = function(event) { switch(event.data.constructor.name) { case "String": { // is a Activity message @@ -150,7 +201,7 @@ channelSocket.onmessage = function(event) { } case "ArrayBuffer": { // is a Command message - let args = ParseCommandString(new TextDecoder("utf8").decode(event.data)); + let args = __messengerjs__.ParseCommandString(new TextDecoder("utf8").decode(event.data)); switch(args[0]) { case "rename": { __messengerjs__.myNickname = args[3]; @@ -158,8 +209,38 @@ channelSocket.onmessage = function(event) { } case "ready": { console.log("joined channel successfully."); + // todo: allow targets to change username, not only initiators __messengerjs__.fade.textContent = "waiting for opponent to join this channel..."; - window.addEventListener("beforeunload", beforeUnloadHandler); + const nameDialog = document.createElement("div"); + nameDialog.style.textAlign = "center"; + const usernameInput = document.createElement("input"); + const usernameButton = document.createElement("button"); + const helpText = document.createElement("p"); + usernameInput.onkeydown = function() { + usernameInput.setCustomValidity(""); + } + usernameInput.style.marginRight = "2px"; + nameDialog.appendChild(usernameInput); + usernameButton.textContent = "Change Username"; + usernameButton.onclick = function() { + if(/^[\w ]{3,32}$/u.test(usernameInput.value.trim())) { + __messengerjs__.channelSocket.send( + new TextEncoder("utf8").encode(__messengerjs__.EncodeCommandString(["rename", usernameInput.value.trim()])) + ); + } else { + usernameInput.setCustomValidity("Username must be between 3 and 32 alphanumeric characters, spaces are allowed."); + usernameInput.reportValidity(); + } + } + usernameButton.style.marginLeft = "2px"; + nameDialog.appendChild(usernameButton); + helpText.textContent = "Usernames can be between 3 and 32 alphanumeric characters with spaces."; + nameDialog.appendChild(helpText); + + __messengerjs__.fade.appendChild(document.createElement("br")); + __messengerjs__.fade.appendChild(document.createElement("br")); + __messengerjs__.fade.appendChild(nameDialog); + window.addEventListener("beforeunload", __messengerjs__.beforeUnloadHandler); __messengerjs__.imTheChannelOwner = (args[1] !== "1"); break; } @@ -170,7 +251,7 @@ channelSocket.onmessage = function(event) { case "0": { // if we're getting an error with STATUS_SUCCESSFUL... which sounds stupid, but i'm using it as SERVER_CLOSING) __messengerjs__.fade.textContent = "the channel server was closed. please try again later."; __messengerjs__.fade.style.display = "block"; - channelSocket.close(1000, "server shutdown acknoledged."); + __messengerjs__.channelSocket.close(1000, "server shutdown acknoledged."); __messengerjs__.callIfExists("Channel_OnTypeChanged"); Channel_OnRemoteAppClosed(); // they're gone Channel_OnAppClose(); // we're gone too @@ -179,14 +260,14 @@ channelSocket.onmessage = function(event) { case "513": { // SERVER_BUSY = channel was full console.warn("channel was full, generating new room"); __messengerjs__.sessionID = Math.random().toString(36).split('.')[1].substring(0,8); - history.pushState(null, "", `#channel=${__messengerjs__.sessionID}`); - channelSocket.send(new TextEncoder("utf8").encode(EncodeCommandString(["connect", __messengerjs__.sessionID, window.GameCode]))); + history.pushState(null, "", "#channel=" + __messengerjs__.sessionID); + __messengerjs__.channelSocket.send(new TextEncoder("utf8").encode(__messengerjs__.EncodeCommandString(["connect", __messengerjs__.sessionID, window.GameCode]))); break; } default: { __messengerjs__.fade.textContent = "server error: " + args[1] + " code (" + args[2] + ")"; __messengerjs__.fade.style.display = "block"; - channelSocket.close(1000, "server sent non-zero error"); + __messengerjs__.channelSocket.close(1000, "server sent non-zero error"); __messengerjs__.callIfExists("Channel_OnTypeChanged"); __messengerjs__.callIfExists("Channel_OnDataError"); // this may just attempt to retry to send the failed data... when we're already closed; } @@ -221,14 +302,14 @@ channelSocket.onmessage = function(event) { case "remuser": { __messengerjs__.fade.textContent = "opponent (" + args[1] + ") disconnected. refresh to get a new session!"; __messengerjs__.fade.style.display = "block"; - window.removeEventListener("beforeunload", beforeUnloadHandler); + window.removeEventListener("beforeunload", __messengerjs__.beforeUnloadHandler); Channel_OnRemoteAppClosed(); - channelSocket.send(new TextEncoder("utf8").encode(EncodeCommandString(["disconnect"]))); - channelSocket.close(1000, "opponent left"); + __messengerjs__.channelSocket.send(new TextEncoder("utf8").encode(__messengerjs__.EncodeCommandString(["disconnect"]))); + __messengerjs__.channelSocket.close(1000, "opponent left"); break; } case "sync": { - channelSocket.send(new TextEncoder("utf8").encode(EncodeCommandString(["sync", (new Date).getTime()]))); + __messengerjs__.channelSocket.send(new TextEncoder("utf8").encode(__messengerjs__.EncodeCommandString(["sync", (new Date).getTime()]))); break; } default: { @@ -239,25 +320,25 @@ channelSocket.onmessage = function(event) { } } } -channelSocket.onerror = function(ev) { +__messengerjs__.channelSocket.onerror = function(ev) { window.external.Channel.Error.Data = "WebSocket generic error"; __messengerjs__.fade.textContent = "encountered a WebSocket error" __messengerjs__.fade.style.display = "block"; __messengerjs__.callIfExists("Channel_OnDataError"); } -channelSocket.onclose = function() { +__messengerjs__.channelSocket.onclose = function() { __messengerjs__.callIfExists("Channel_OnTypeChanged"); } function createWSEventsNowThatImReady() { - if(channelSocket.readyState === 1) { + if(__messengerjs__.channelSocket.readyState === 1) { // if we're already there - channelSocket.send(new TextEncoder("utf8").encode(EncodeCommandString(["rename"]))); - channelSocket.send(new TextEncoder("utf8").encode(EncodeCommandString(["connect", __messengerjs__.sessionID, window.GameCode]))); + __messengerjs__.channelSocket.send(new TextEncoder("utf8").encode(__messengerjs__.EncodeCommandString(["rename"]))); + __messengerjs__.channelSocket.send(new TextEncoder("utf8").encode(__messengerjs__.EncodeCommandString(["connect", __messengerjs__.sessionID, window.GameCode]))); } - channelSocket.onopen = function() { - channelSocket.send(new TextEncoder("utf8").encode(EncodeCommandString(["rename"]))); - channelSocket.send(new TextEncoder("utf8").encode(EncodeCommandString(["connect", __messengerjs__.sessionID, window.GameCode]))); + __messengerjs__.channelSocket.onopen = function() { + __messengerjs__.channelSocket.send(new TextEncoder("utf8").encode(__messengerjs__.EncodeCommandString(["rename"]))); + __messengerjs__.channelSocket.send(new TextEncoder("utf8").encode(__messengerjs__.EncodeCommandString(["connect", __messengerjs__.sessionID, window.GameCode]))); } } /** pure evil **/ @@ -267,7 +348,7 @@ __messengerjs__.OnLoadInterrupt = function(ev) { __messengerjs__.sessionID = new URL(location.href).hash.substring(1).split("=")[1]; } else { __messengerjs__.sessionID = Math.random().toString(36).split('.')[1].substring(0,8); - history.pushState(null, "", `#channel=${__messengerjs__.sessionID}`); + history.pushState(null, "", "#channel=" + __messengerjs__.sessionID); } createWSEventsNowThatImReady(); __messengerjs__.onloadargs = ev; diff --git a/server.js b/server.js index 16e5aca..e935dab 100644 --- a/server.js +++ b/server.js @@ -22,7 +22,11 @@ const ParseCommandString = function(instruction) { break; } - position = (parseInt(instruction.slice(position + 1, length)) + length) + 1; // todo: not overflow + position = (parseInt(instruction.slice(position + 1, length)) + length) + 1; + if(position > instruction.length) { + return; + } + sections.push(instruction.slice(length + 1, position) .replace(/'/g, "'") .replace(/"/g, '"') @@ -49,12 +53,12 @@ const EncodeCommandString = function(sections) { argv[argc] = sections[argc].toString(); } argv[argc] = argv[argc] - .replace(/'/g, "'") - .replace(/"/g, '"') - .replace(/\//g, '/' ) - .replace(//g, '>' ) .replace(/&/g, '&' ) + .replace(/>/g, '>' ) + .replace(/ 32) { // if it's too long + var nickname = (args.length > 1) ? args[1] : `guest${Math.floor(Math.random() * 99999)}`; + if(nickname.length < 3 || nickname.length > 32) { // if it's too short or long nickname = `guest${Math.floor(Math.random() * 99999)}`; } // this should probably be a while or something (only do this if the name exists) @@ -81,6 +84,10 @@ const MessageParser = function(webSocket, message, isBinary) { nickname = `guest${Math.floor(Math.random() * 99999)}`; } } + if(UserAttachedNameStorage.has(webSocket.getUserData().uid)) { + console.log(`user "${UserAttachedNameStorage.get(webSocket.getUserData().uid)}" (${webSocket.getUserData().uid}) renamed to "${nickname}"`); + ReverseNameToUidStorage.delete(UserAttachedNameStorage.get(webSocket.getUserData().uid)); + } UserAttachedNameStorage.set(webSocket.getUserData().uid, nickname); ReverseNameToUidStorage.set(nickname, webSocket.getUserData().uid); webSocket.send(EncodeCommandString(["rename", 0, "", nickname]), true); @@ -123,7 +130,7 @@ const MessageParser = function(webSocket, message, isBinary) { webSocket.send(EncodeCommandString(["error", "already in another channel", 797]), true); break; } - console.log(`user ${UserAttachedNameStorage.get(webSocket.getUserData().uid)} (${webSocket.getUserData().uid}) joined #${getChannelId}`); + console.log(`user "${UserAttachedNameStorage.get(webSocket.getUserData().uid)}" (${webSocket.getUserData().uid}) joined #${getChannelId}`); webSocket.send(EncodeCommandString(["ready", server.numSubscribers(`channels/${getChannelId}`)]), true); webSocket.subscribe(`channels/${getChannelId}`); UserAttachedChannelStorage.set(webSocket.getUserData().uid, getChannelId); @@ -138,7 +145,7 @@ const MessageParser = function(webSocket, message, isBinary) { } case "disconnect": { if(UserAttachedChannelStorage.has(webSocket.getUserData().uid) === true) { - console.log(`user ${UserAttachedNameStorage.get(webSocket.getUserData().uid)} (${webSocket.getUserData().uid}) left #${UserAttachedChannelStorage.get(webSocket.getUserData().uid)}`); + console.log(`user "${UserAttachedNameStorage.get(webSocket.getUserData().uid)}" (${webSocket.getUserData().uid}) left #${UserAttachedChannelStorage.get(webSocket.getUserData().uid)}`); webSocket.unsubscribe(`channels/${UserAttachedChannelStorage.get(webSocket.getUserData().uid)}`); webSocket.publish( `channels/${UserAttachedChannelStorage.get(webSocket.getUserData().uid)}`, @@ -150,6 +157,8 @@ const MessageParser = function(webSocket, message, isBinary) { console.log(`deleting #${UserAttachedChannelStorage.get(webSocket.getUserData().uid)} since (${webSocket.getUserData().uid}) left, leaving it with no members`); ChannelStorage.delete(UserAttachedChannelStorage.get(webSocket.getUserData().uid)); } + ReverseNameToUidStorage.delete(UserAttachedNameStorage.get(webSocket.getUserData().uid)); + UserAttachedNameStorage.delete(webSocket.getUserData().uid); UserAttachedChannelStorage.delete(webSocket.getUserData().uid); // user is disconnecting next frame! }