const channelSocket = new WebSocket("ws://localhost:9091/connect"); 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") { return; } let position = -1; let sections = new Array(); for(;;) { let length = instruction.indexOf('.', position + 1); if(length === -1) { break; } position = (parseInt(instruction.slice(position + 1, length)) + length) + 1; // todo: not overflow sections.push(instruction.slice(length + 1, position) .replace(/'/g, "'") .replace(/"/g, '"') .replace(///g, '/') .replace(/</g, '<') .replace(/>/g, '>') .replace(/&/g, '&') ); if(instruction.slice(position, position + 1) === ';') { break; } } return sections; } const EncodeCommandString = function(sections) { if(Array.isArray(sections) === false) { return; } let instruction = new String(); const argv = sections; for (let argc = 0; argc < sections.length ; argc++) { if(typeof sections[argc] !== "string") { argv[argc] = sections[argc].toString(); } argv[argc] = argv[argc] .replace(/'/g, "'") .replace(/"/g, '"') .replace(/\//g, '/' ) .replace(//g, '>' ) .replace(/&/g, '&' ) ; instruction = instruction.concat(argv[argc].length.toString(), ".", argv[argc]) instruction += (argc === sections.length - 1) ? ';' : ','; } return instruction; } /** window.external **/ window.external.CloseApp = function() { channelSocket.close(1000, "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.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) { __messengerjs__sessionID = Math.random().toString(36).split('.')[1].substring(0,8); history.pushState(null, "", `?channel=${__messengerjs__sessionID}`); } else { __messengerjs__sessionID = new URL(location.href).searchParams.get("channel"); } createWSEventsNowThatImReady(); }; Object.defineProperty(window.external.Channel, "Type", { get: function Type() { if(channelSocket.readyState === 1) { return 1; // Indirect; } else { return 2; // Disconnected; } } }); /** 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... 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"} ]; 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; // 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.Inviter = window.external.yellows111_Users[0]; document.title = "Initiator: " + document.title; } else { 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"; // the alternative is blob.arrayBuffer(); IT'S A PROMISE CALL AND PROMISES SUXX channelSocket.onmessage = function(event) { switch(event.data.constructor.name) { case "String": { // is a Activity message window.external.Channel.Data = event.data; Channel_OnDataReceived(); break; } case "ArrayBuffer": { // is a Command message let args = ParseCommandString(new TextDecoder("utf8").decode(event.data)); switch(args[0]) { case "rename": { __messengerjs__myNickname = args[3]; break; } case "connect": { switch(args[1]) { 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": { console.log("joined room successfully."); if(args[2] === "1") { __messengerjs__imTheRoomOwner = false; } else { __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("disconnected gracefully."); break; } } break; } case "disconnect": { channelSocket.close(1000, "goodbye"); Channel_OnTypeChanged(); } case "adduser": { 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 "remuser": { // todo? window.removeEventListener("beforeunload", beforeUnloadHandler); Channel_OnRemoteAppClosed(); beforeUnloadHandler(null, "other user disconnected"); // disconnect ourselves break; } case "sync": { channelSocket.send(new TextEncoder("utf8").encode(EncodeCommandString(["sync", (new Date).getTime()]))); break; } default: { console.warn("unknown instruction sequence:", args); } } break; } } } channelSocket.onerror = function(ev) { window.external.Channel.Error.Data = "WebSocket generic error"; Channel_OnDataError(); } channelSocket.onclose = function() { 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", __messengerjs__sessionID]))); } channelSocket.onopen = function() { channelSocket.send(new TextEncoder("utf8").encode(EncodeCommandString(["rename"]))); channelSocket.send(new TextEncoder("utf8").encode(EncodeCommandString(["connect", __messengerjs__sessionID]))); } }