const channelSocket = new WebSocket("ws://localhost:9091/connect"); let sessionID = null; let imTheRoomOwner = false; let myNickname = ""; /** @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}; // "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}`); } else { 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": "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"} ]; window.external.Users = {"Me": null, "me": null, "Inviter": null, "Item": null}; // "Count" is a defined property 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; } }); 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.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": { myNickname = args[3]; break; } case "connect": { switch(args[1]) { case 0: { console.error("room is full or not avaliable"); break; } case 1: { console.log("joined room successfully."); if(parseInt(args[2])) { imTheRoomOwner = false; } else { imTheRoomOwner = true; //window.external.Users.Inviter = window.external.Users.Me; } break; } case 2: { console.log("disconnecting?"); break; } } break; } case "adduser": { if(imTheRoomOwner === false) { window.external.yellows111_Users[1].Name = myNickname; } window.external.yellows111_Users[0].Name = args[2]; Channel_OnRemoteAppLoaded(); Channel_OnTypeChanged(); break; } case "deluser": { // todo? Channel_OnTypeChanged(); 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() { Channel_OnDataError(); } channelSocket.onclose = function() { Channel_OnAppClose(); } 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.onopen = function() { channelSocket.send(new TextEncoder("utf8").encode(EncodeCommandString(["rename"]))); channelSocket.send(new TextEncoder("utf8").encode(EncodeCommandString(["connect", sessionID]))); } }