diff --git a/client.js b/client.js index c7995f4..9b1f5cc 100644 --- a/client.js +++ b/client.js @@ -1,7 +1,12 @@ const channelSocket = new WebSocket("ws://localhost:9091/connect"); -let sessionID = null; -let imTheRoomOwner = false; -let myNickname = ""; +var __messengerjs__sessionID = null; +var __messengerjs__imTheRoomOwner = false; +var __messengerjs__myNickname = ""; +const beforeUnloadHandler = function(ev, reason) { + Channel_OnAppClose(); + channelSocket.send(new TextEncoder("utf8").encode(EncodeCommandString(["disconnect"]))); + channelSocket.close(1000, ((typeof (reason)) === "string") ? reason : "unloading"); +}; /** @section guacamole **/ const ParseCommandString = function(instruction) { if(typeof instruction !== "string") { @@ -63,14 +68,14 @@ window.external.CloseApp = function() { } // this is important because things expect it /** window.external.Channel **/ -window.external.Channel = {"Data": "", "SendData": null, "Initialize": null}; // "Type" is a defined property +window.external.Channel = {"Data": "", "SendData": null, "Initialize": null, "Error": {"Data": ""}}; // "Type" is a defined property 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() { if(new URL(location.href).searchParams.get("channel") === null) { - sessionID = Math.random().toString(36).split('.')[1].substring(0,8); - history.pushState(null, "", `?channel=${sessionID}`); + __messengerjs__sessionID = Math.random().toString(36).split('.')[1].substring(0,8); + history.pushState(null, "", `?channel=${__messengerjs__sessionID}`); } else { - sessionID = new URL(location.href).searchParams.get("channel"); + __messengerjs__sessionID = new URL(location.href).searchParams.get("channel"); } createWSEventsNowThatImReady(); }; @@ -87,32 +92,42 @@ Object.defineProperty(window.external.Channel, "Type", { // 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... window.external.yellows111_Users = [ - {"Name": "Initiator", "Email": "you@messenger.js", "GlobalIP": "0.0.0.0", "LocalIP": "0.0.0.0", "PUID": "0"}, - {"Name": "Target", "Email": "opponent@messenger.js", "GlobalIP": "0.0.0.0", "LocalIP": "0.0.0.1", "PUID": "1"} + {"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"} ]; -window.external.Users = {"Me": null, "me": null, "Inviter": null, "Item": null}; // "Count" is a defined property + +window.external.Users = {"Me": null, "Inviter": null, "Item": null}; // "Count" is a defined property +/** window.external.Users._NewEnum **/ +window.external.Users[Symbol.iterator] = function*() { + yield window.external.yellows111_Users[0]; + yield window.external.yellows111_Users[1]; +}; window.external.Users.Item = function(which){ return window.external.yellows111_Users[which]; } Object.defineProperty(window.external.Users, "Count", { get: function Count() { - return window.external.yellows111_Users.length; + return window.external.yellows111_Users.length; // 2 + } +}); +/** Some early activities use a lower case "Me", support that like this... **/ +Object.defineProperty(window.external.Users, "me", { + get: function me() { + return window.external.Users.Me; } }); if(new URL(location.href).searchParams.get("channel") === null) { window.external.Users.Me = window.external.yellows111_Users[0]; - window.external.Users.me = window.external.yellows111_Users[0]; window.external.Users.Inviter = window.external.yellows111_Users[0]; document.title = "Initiator: " + document.title; } else { window.external.Users.Me = window.external.yellows111_Users[1]; - window.external.Users.me = window.external.yellows111_Users[1]; window.external.Users.Inviter = window.external.yellows111_Users[0]; document.title = "Target: " + document.title; } /** websocket stuff **/ -channelSocket.binaryType = "arraybuffer"; // what's the point of doing Blob.arrayBuffer every binary call... which is awful because IT'S A PROMISE CALL AND PROMISES SUXX +channelSocket.binaryType = "arraybuffer"; // the alternative is blob.arrayBuffer(); IT'S A PROMISE CALL AND PROMISES SUXX channelSocket.onmessage = function(event) { switch(event.data.constructor.name) { case "String": { @@ -126,44 +141,59 @@ channelSocket.onmessage = function(event) { let args = ParseCommandString(new TextDecoder("utf8").decode(event.data)); switch(args[0]) { case "rename": { - myNickname = args[3]; + __messengerjs__myNickname = args[3]; break; } case "connect": { switch(args[1]) { - case 0: { - console.error("room is full or not avaliable"); + case "0": { + console.error("room is full or not avaliable"); + beforeUnloadHandler(null, "room was not avaliable"); + history.replaceState(null, "", window.location.href.split("?")[0]); + window.location.reload(); // it's jank and probably should just be handled socket side break; } - case 1: { + case "1": { console.log("joined room successfully."); - if(parseInt(args[2])) { - imTheRoomOwner = false; + if(args[2] === "1") { + __messengerjs__imTheRoomOwner = false; } else { - imTheRoomOwner = true; - //window.external.Users.Inviter = window.external.Users.Me; + __messengerjs__imTheRoomOwner = true; + if(window.external.Users.Inviter !== window.external.Users.Me) { + // This room has already been closed. Create a new one. + beforeUnloadHandler(null, "room 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 + } } break; } - case 2: { - console.log("disconnecting?"); + case "2": { + console.log("disconnected gracefully."); break; } } break; } + case "disconnect": { + channelSocket.close(1000, "goodbye"); + Channel_OnTypeChanged(); + } case "adduser": { - if(imTheRoomOwner === false) { - window.external.yellows111_Users[1].Name = myNickname; + if(__messengerjs__imTheRoomOwner === false) { + window.external.yellows111_Users[1].Name = __messengerjs__myNickname; } window.external.yellows111_Users[0].Name = args[2]; + window.addEventListener("beforeunload", beforeUnloadHandler); Channel_OnRemoteAppLoaded(); Channel_OnTypeChanged(); break; } - case "deluser": { + case "remuser": { // todo? - Channel_OnTypeChanged(); + window.removeEventListener("beforeunload", beforeUnloadHandler); + Channel_OnRemoteAppClosed(); + beforeUnloadHandler(null, "other user disconnected"); // disconnect ourselves break; } case "sync": { @@ -178,20 +208,21 @@ channelSocket.onmessage = function(event) { } } } -channelSocket.onerror = function() { +channelSocket.onerror = function(ev) { + window.external.Channel.Error.Data = "WebSocket generic error"; Channel_OnDataError(); } channelSocket.onclose = function() { - Channel_OnAppClose(); + Channel_OnTypeChanged(); } function createWSEventsNowThatImReady() { if(channelSocket.readyState === 1) { // if we're already there channelSocket.send(new TextEncoder("utf8").encode(EncodeCommandString(["rename"]))); - channelSocket.send(new TextEncoder("utf8").encode(EncodeCommandString(["connect", sessionID]))); + channelSocket.send(new TextEncoder("utf8").encode(EncodeCommandString(["connect", __messengerjs__sessionID]))); } channelSocket.onopen = function() { channelSocket.send(new TextEncoder("utf8").encode(EncodeCommandString(["rename"]))); - channelSocket.send(new TextEncoder("utf8").encode(EncodeCommandString(["connect", sessionID]))); + channelSocket.send(new TextEncoder("utf8").encode(EncodeCommandString(["connect", __messengerjs__sessionID]))); } } \ No newline at end of file diff --git a/server.js b/server.js index d76e808..661d22e 100644 --- a/server.js +++ b/server.js @@ -99,6 +99,7 @@ const MessageParser = function(webSocket, message, isBinary) { } if(typeof ChannelStorage.get(getChannelId) === "undefined") { // make the channel right then and there + console.log(`creating #${getChannelId}`); ChannelStorage.set(getChannelId, {"owner": webSocket.getUserData().uid}); }; if(webSocket.isSubscribed(`channels/${getChannelId}`) === true) { @@ -111,18 +112,39 @@ const MessageParser = function(webSocket, message, isBinary) { webSocket.send(EncodeCommandString(["connect", 0]), 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.subscribe(`channels/${getChannelId}`); UserAttachedChannelStorage.set(webSocket.getUserData().uid, getChannelId); // do we keep adduser? if(ChannelStorage.get(getChannelId).owner !== webSocket.getUserData().uid) { - // send other user's presence if we don't own the channel (if we're not owner) + // send other user's presense if we don't own the channel (if we're not owner) webSocket.send(EncodeCommandString(["adduser", 1, UserAttachedNameStorage.get(ChannelStorage.get(getChannelId).owner)]), true); } // send to other user (or nobody) that we joined this channel webSocket.publish(`channels/${getChannelId}`, EncodeCommandString(["adduser", 1, UserAttachedNameStorage.get(webSocket.getUserData().uid)]), true); break; } + case "disconnect": { + if(typeof UserAttachedChannelStorage.get(webSocket.getUserData().uid) !== "undefined") { + 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)}`, + EncodeCommandString(["remuser", UserAttachedNameStorage.get(webSocket.getUserData().uid)]), + true + ); + // if room is empty, delete it + if(server.numSubscribers(`channels/${UserAttachedChannelStorage.get(webSocket.getUserData().uid)}`) === 0) { + console.log(`deleting #${UserAttachedChannelStorage.get(webSocket.getUserData().uid)}`); + ChannelStorage.delete(UserAttachedChannelStorage.get(webSocket.getUserData().uid)); + } + UserAttachedChannelStorage.delete(webSocket.getUserData().uid); + } + webSocket.send(EncodeCommandString(["connect", 2]), true); // graceful disconnect message + webSocket.send(EncodeCommandString(["disconnect"]), true); + return; + } case "sync": { // client is alive... break;