const channelSocket = new WebSocket("ws://localhost:9091/connect"); const __messengerjs__ = { "sessionID": null, "imTheChannelOwner": false, "myNickname": "", "onloadfunction": null, "onloadargs": null, "fade": null } __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..."; __messengerjs__.callIfExists = function(fnName) { // THIS function exists because some of the events we call may not exist on the client... why? if(typeof window[fnName] === "function") { window[fnName](); } } const beforeUnloadHandler = function(ev) { Channel_OnAppClose(); channelSocket.send(new TextEncoder("utf8").encode(EncodeCommandString(["disconnect"]))); channelSocket.close(1000, "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() { 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": 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 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) { return 1; // Indirect; } else { return 2; // Disconnected; } } }); /** window.external.Users **/ // 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"} ]; 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; } }); /* 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) { 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 "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."); __messengerjs__.callIfExists("Channel_OnTypeChanged"); Channel_OnRemoteAppClosed(); // they're gone Channel_OnAppClose(); // we're gone too break; } 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]))); 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__.callIfExists("Channel_OnTypeChanged"); __messengerjs__.callIfExists("Channel_OnDataError"); // this may just attempt to retry to send the failed data... when we're already closed; } } break; } case "adduser": { __messengerjs__.fade.style.display = "none"; if(__messengerjs__.imTheChannelOwner === false) { window.external.yellows111_Users[1].Name = __messengerjs__.myNickname; window.external.yellows111_Users[0].Name = args[2]; } else { window.external.yellows111_Users[1].Name = args[2]; window.external.yellows111_Users[0].Name = __messengerjs__.myNickname; } if(__messengerjs__.imTheChannelOwner === true) { window.external.Users.Me = window.external.yellows111_Users[0]; window.external.Users.Inviter = window.external.yellows111_Users[0]; } else { window.external.Users.Me = window.external.yellows111_Users[1]; window.external.Users.Inviter = window.external.yellows111_Users[0]; } document.title = __messengerjs__.myNickname + ": " + document.title; /** beyond even hack territory **/ if(typeof __messengerjs__.onloadfunction === "function") { __messengerjs__.onloadfunction(__messengerjs__.onloadargs); } Channel_OnRemoteAppLoaded(); __messengerjs__.callIfExists("Channel_OnTypeChanged"); break; } case "remuser": { __messengerjs__.fade.textContent = "opponent (" + args[1] + ") disconnected. refresh to get a new session!"; __messengerjs__.fade.style.display = "block"; window.removeEventListener("beforeunload", beforeUnloadHandler); Channel_OnRemoteAppClosed(); channelSocket.send(new TextEncoder("utf8").encode(EncodeCommandString(["disconnect"]))); channelSocket.close(1000, "opponent left"); 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"; __messengerjs__.fade.textContent = "encountered a WebSocket error" __messengerjs__.fade.style.display = "block"; Channel_OnDataError(); } channelSocket.onclose = function() { __messengerjs__.callIfExists("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, 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]))); } } /** pure evil **/ __messengerjs__.OnLoadInterrupt = function(ev) { document.body.appendChild(__messengerjs__.fade); if(new URL(location.href).hash.substring(1).split("=")[0] === "channel") { __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}`); } createWSEventsNowThatImReady(); __messengerjs__.onloadargs = ev; } 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; } window.onload = __messengerjs__.OnLoadInterrupt; Object.defineProperty(window, "onload", { set: function onload(fn) { __messengerjs__.onloadfunction = fn; } }); });