From 3770586592e79d7ad153da473553e3e21fc89e07 Mon Sep 17 00:00:00 2001 From: yellows111 Date: Wed, 28 Feb 2024 13:41:20 +0000 Subject: [PATCH] beta 4: using more guacamolean command names a bit less extended and using stock error and ready instead of connect,<0,1,2> this is semver breaking but we're in prerelease so i'm not touching package.json ... until it's considered at least mostly done --- client.js | 93 +++++++++++++++++++++++++++++++------------------------ server.js | 32 +++++++++++++------ 2 files changed, 74 insertions(+), 51 deletions(-) diff --git a/client.js b/client.js index c89c8bf..6f3a2e9 100644 --- a/client.js +++ b/client.js @@ -11,11 +11,12 @@ __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..."; -const beforeUnloadHandler = function(ev, reason) { +const beforeUnloadHandler = function(ev) { Channel_OnAppClose(); channelSocket.send(new TextEncoder("utf8").encode(EncodeCommandString(["disconnect"]))); - channelSocket.close(1000, ((typeof (reason)) === "string") ? reason : "unloading"); + channelSocket.close(1000, "unloading"); }; + /** @section guacamole **/ const ParseCommandString = function(instruction) { if(typeof instruction !== "string") { @@ -73,14 +74,18 @@ const EncodeCommandString = function(sections) { /** window.external **/ window.external.CloseApp = function() { - beforeUnloadHandler(null, "called CloseApp"); + Channel_OnAppClose(); + channelSocket.send(new TextEncoder("utf8").encode(EncodeCommandString(["disconnect"]))); + channelSocket.close(1000, "we called CloseApp"); } // this is important because things expect it /** window.external.Channel **/ -window.external.Channel = {"Data": "", "SendData": null, "Initialize": null, "Error": {"Data": ""}}; // "Type" is a defined property +window.external.Channel = {"Data": "", "SendData": null, "Initialize": null, "Error": {"Data": "", "Type": 0}}; // "Type" is a defined property +// things may want Channel.IM: string and Channel.FileInfo: object {Path: string, Size: number, Progress: number<0-100>, 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.Initialize = function() { - // no idea + // no idea we're already pretty initalized by the time most things call this + Channel_OnTypeChanged(); // make client check if its even connected still }; Object.defineProperty(window.external.Channel, "Type", { get: function Type() { @@ -92,8 +97,9 @@ Object.defineProperty(window.external.Channel, "Type", { } }); /** window.external.Users **/ -// This sadly needs to be already configured before an activity tries using it... -// We can't just block execution until we've got the session... +// yes i define these as the default users... should be created a bit more dynamically +// problem with that is that i'm yet to find a 1 user activity that calls any of these... ReplaceIM abuse maybe? +// avaliablity of these properties depends on the permission flags, especially UserProperties being enabled window.external.yellows111_Users = [ {"Name": "Initiator", "Email": "initiator@messenger.js", "GlobalIP": "0.0.0.0", "LocalIP": "0.0.0.0", "PUID": "0"}, {"Name": "Target", "Email": "target@messenger.js", "GlobalIP": "0.0.0.0", "LocalIP": "0.0.0.1", "PUID": "1"} @@ -120,6 +126,12 @@ Object.defineProperty(window.external.Users, "me", { } }); +/* + window.external.Messenger exists but all it does is: + * allow you to open the Options dialog (on a certain page: number) + * allow you to open the phone dialer (with a predetermined number: string) + 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) { @@ -138,45 +150,43 @@ channelSocket.onmessage = function(event) { __messengerjs__.myNickname = args[3]; break; } - case "connect": { - switch(args[1]) { - case "0": { - console.error("channel is full or not avaliable"); - // regenerate channel ID if we get 0'd - __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]))); + case "ready": { + console.log("joined channel successfully."); + __messengerjs__.fade.textContent = "waiting for opponent to join this channel..."; + window.addEventListener("beforeunload", beforeUnloadHandler); + __messengerjs__.imTheChannelOwner = (args[1] !== "1"); + break; + } + case "error": { + window.external.Channel.Error.Data = args[1]; + window.external.Channel.Error.Type = parseInt(args[2]); + switch(args[2]) { + 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."); + Channel_OnTypeChanged(); + Channel_OnRemoteAppClosed(); // they're gone + Channel_OnAppClose(); // we're gone too break; } - case "1": { - console.log("joined channel successfully."); - __messengerjs__.fade.textContent = "waiting for opponent to join this channel..."; - window.addEventListener("beforeunload", beforeUnloadHandler); - if(args[2] === "1") { - __messengerjs__.imTheChannelOwner = false; - } else { - __messengerjs__.imTheChannelOwner = true; - if(window.external.Users.Inviter !== window.external.Users.Me) { - // This channel has already been closed. Create a new one. - beforeUnloadHandler(null, "channel was empty when expecting host"); - history.replaceState(null, "", window.location.href.split("?")[0]); - window.location.reload(); // it's still jank and probably should just be handled elsewhere - } - } + 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]))); break; } - case "2": { - console.log("disconnected gracefully."); - 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"); + Channel_OnTypeChanged(); + Channel_OnDataError(); // this may just attempt to retry to send the failed data... when we're already closed; } } break; } - case "disconnect": { - // code will never reach here normally (should announce from server on ^C, though) - channelSocket.close(1000, "goodbye"); - Channel_OnTypeChanged(); - } case "adduser": { __messengerjs__.fade.style.display = "none"; if(__messengerjs__.imTheChannelOwner === false) { @@ -207,7 +217,8 @@ channelSocket.onmessage = function(event) { __messengerjs__.fade.style.display = "block"; window.removeEventListener("beforeunload", beforeUnloadHandler); Channel_OnRemoteAppClosed(); - beforeUnloadHandler(null, "other user disconnected"); // disconnect ourselves + channelSocket.send(new TextEncoder("utf8").encode(EncodeCommandString(["disconnect"]))); + channelSocket.close(1000, "opponent left"); break; } case "sync": { @@ -255,8 +266,8 @@ __messengerjs__.OnLoadInterrupt = function(ev) { createWSEventsNowThatImReady(); __messengerjs__.onloadargs = ev; } -document.addEventListener("DOMContentLoaded", (event) => { - // do document.body.onload too since some things suck +document.addEventListener("DOMContentLoaded", function(event) { + // do document.body.onload too since some things suck and like doing that if(typeof document.body.onload === "function") { __messengerjs__.onloadfunction = document.body.onload; } diff --git a/server.js b/server.js index 0194f8c..fba9ac5 100644 --- a/server.js +++ b/server.js @@ -88,18 +88,18 @@ const MessageParser = function(webSocket, message, isBinary) { } case "connect": { if(args.length <= 1) { - webSocket.send(EncodeCommandString(["connect", 0]), true); + webSocket.send(EncodeCommandString(["error", "need more arguments", 768]), true); break; } let getChannelId = args[1]; if(server.numSubscribers(`channels/${getChannelId}`) >= 2) { // this room is full - webSocket.send(EncodeCommandString(["connect", 0]), true); + webSocket.send(EncodeCommandString(["error", "channel has too many members", 513]), true); break; } if(UserAttachedNameStorage.has(webSocket.getUserData().uid) === false) { // user doesn't have a username - webSocket.send(EncodeCommandString(["connect", 0]), true); + webSocket.send(EncodeCommandString(["error", "you need a nickname", 769]), true); break; } if(ChannelStorage.has(getChannelId) === false) { @@ -109,16 +109,16 @@ const MessageParser = function(webSocket, message, isBinary) { }; if(webSocket.isSubscribed(`channels/${getChannelId}`) === true) { // already in this channel - webSocket.send(EncodeCommandString(["connect", 0]), true); + webSocket.send(EncodeCommandString(["error", "already in this channel", 517]), true); break; }; if(UserAttachedChannelStorage.has(webSocket.getUserData().uid) === true) { // already in a channel that isn't the one we're trying to join - webSocket.send(EncodeCommandString(["connect", 0]), true); + webSocket.send(EncodeCommandString(["error", "already in another channel", 797]), true); break; } console.log(`user ${UserAttachedNameStorage.get(webSocket.getUserData().uid)} (${webSocket.getUserData().uid}) joined #${getChannelId}`); - webSocket.send(EncodeCommandString(["connect", 1, server.numSubscribers(`channels/${getChannelId}`)]), true); + webSocket.send(EncodeCommandString(["ready", server.numSubscribers(`channels/${getChannelId}`)]), true); webSocket.subscribe(`channels/${getChannelId}`); UserAttachedChannelStorage.set(webSocket.getUserData().uid, getChannelId); // do we keep adduser? @@ -145,13 +145,12 @@ const MessageParser = function(webSocket, message, isBinary) { ChannelStorage.delete(UserAttachedChannelStorage.get(webSocket.getUserData().uid)); } UserAttachedChannelStorage.delete(webSocket.getUserData().uid); + // user is disconnecting next frame! } - webSocket.send(EncodeCommandString(["connect", 2]), true); // graceful disconnect message - webSocket.send(EncodeCommandString(["disconnect"]), true); return; } case "sync": { - // client is alive... + // client is alive... TODO check if this is too ahead/behind break; } default: { @@ -169,6 +168,7 @@ const MessageParser = function(webSocket, message, isBinary) { webSocket.publish(`channels/${UserAttachedChannelStorage.get(webSocket.getUserData().uid)}`, message, false); // ask other user if alive still webSocket.publish(`channels/${UserAttachedChannelStorage.get(webSocket.getUserData().uid)}`, EncodeCommandString(["sync", (new Date).getTime()]), true); + webSocket.ping("pulse"); } //console.log(`${new TextDecoder("utf-8").decode(webSocket.getRemoteAddressAsText())} message: ${msg}`); } @@ -176,6 +176,8 @@ const UserStateRequest = function(webSocket, state, data = null) { switch(state) { case "open": { //console.log("%s client hello", new TextDecoder("utf-8").decode(webSocket.getRemoteAddressAsText())); + webSocket.subscribe("system"); + webSocket.ping("welcome"); break; } case "close": { @@ -207,4 +209,14 @@ server.ws("/connect", { "close": function(ws, code, message) {UserStateRequest(ws, "close", {code, message})}, "drain": function(ws) {console.log(`WS going through back-pressure!: ${ws.getBufferedAmount()}`)} }); -server.listen(config.port, function(token) {console.log(token ? `open on ${config.port}` : `failed to listen to ${config.port}`)}); \ No newline at end of file +server.listen(config.port, function(token) {console.log(token ? `open on ${config.port}` : `failed to listen to ${config.port}`)}); + +var serverIsClosing = false; +require("process").on('SIGINT', function() { + if(serverIsClosing) {return}; + server.publish("system", EncodeCommandString(["error", "server is closing", 0]), true); + serverIsClosing = true; + require("process").on('SIGINT', function(){console.warn("emergency shutdown was engaged, clients may be confused!");require("process").exit()}); + console.log("shutting down server, giving clients 5 seconds..."); + setTimeout(function(){console.log("server stopping now");server.close();require("process").exit()}, 5000) +}); \ No newline at end of file