messenger.js/client.js

259 lines
9.2 KiB
JavaScript

const channelSocket = new WebSocket("ws://localhost:9091/connect");
var __messengerjs__sessionID = null;
var __messengerjs__imTheChannelOwner = false;
var __messengerjs__myNickname = "";
var __messengerjs__onloadfunction = null;
var __messengerjs__onloadargs = null;
const __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) {
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(/&lt;/g, '<')
.replace(/&gt;/g, '>')
.replace(/&amp;/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, "&#x27;")
.replace(/"/g, '&quot;')
.replace(/\//g, '&#x2F' )
.replace(/</g, '&lt;' )
.replace(/>/g, '&gt;' )
.replace(/&/g, '&amp;' )
;
instruction = instruction.concat(argv[argc].length.toString(), ".", argv[argc])
instruction += (argc === sections.length - 1) ? ';' : ',';
}
return instruction;
}
/** window.external **/
window.external.CloseApp = function() {
beforeUnloadHandler(null, "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() {
// no idea
};
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;
}
});
/** 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("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])));
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
}
}
break;
}
case "2": {
console.log("disconnected gracefully.");
break;
}
}
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) {
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();
Channel_OnTypeChanged();
break;
}
case "remuser": {
__messengerjs__fade.textContent = "opponent (" + args[1] + ") disconnected :(";
__messengerjs__fade.style.display = "revert";
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";
__messengerjs__fade.textContent = "encountered a WebSocket error"
__messengerjs__fade.style.display = "revert";
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])));
}
}
/** pure evil **/
window.onload = function(ev) {
document.body.append(__messengerjs__fade);
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();
__messengerjs__onloadargs = ev;
}
Object.defineProperty(window, "onload", {
set: function onload(fn) {
__messengerjs__onloadfunction = fn;
}
});