import {Logger} from "./logger"; import { MessageC2SGoodbye, MessageC2SGoodbye_packetInfo, MessageC2SHello, MessageC2SHello_packetInfo, MessageC2SPing, MessageC2SPing_packetInfo } from "./protocol/message_c2s"; import {State} from "./protocol/state"; import {decode, encode} from "./serde"; import { MessageS2CChat, MessageS2CChat_packetInfo, MessageS2CGoodbye, MessageS2CGoodbye_packetInfo, MessageS2CHello, MessageS2CHello_packetInfo, MessageS2CModulesUpdate, MessageS2CModulesUpdate_packetInfo, MessageS2CPlanetData, MessageS2CPlanetData_packetInfo, MessageS2CPlayersUpdate, MessageS2CPlayersUpdate_packetInfo, MessageS2CPong_packetInfo } from "./protocol/message_s2c"; import {GoodbyeReason} from "./protocol/goodbye_reason"; import {global} from "./index"; const logger = new Logger("Gateway"); export interface GatewayClient { state: State; socket: WebSocket; username: string | null; version: number | null; ping_timeout: Timeout | null; // i am aware that these types dont exist ping_timeout_left: Timeout; // its fine } export async function gateway_connect(gateway_url: string, username: string): GatewayClient { logger.info("FAST CONNECT - Connecting to gateway socket at " + gateway_url); let ws = await _websocket_connect(gateway_url); logger.debug("[fastconnect] connected to gateway, performing handshake with server"); let client: GatewayClient = { state: State.Handshake, socket: ws, username: null, version: null, ping_timeout: null, ping_timeout_left: null }; let ping_timeout_fn = () => { logger.error("Server didn't send back pong in time."); let cya = MessageC2SGoodbye.encode({reason: GoodbyeReason.PingPongTimeout}).finish(); client.socket.send(encode(MessageC2SGoodbye_packetInfo.type, cya)); client.state = State.Handshake; client.username = null; client.version = null; client.socket.close(); } client.ping_timeout_left = setTimeout(ping_timeout_fn, 10 * 1000); let ping_fn = () => { let ping = MessageC2SPing.encode({}).finish(); client.socket.send(encode(MessageC2SPing_packetInfo.type, ping)); setTimeout(ping_fn, 5 * 1000); } client.ping_timeout = setTimeout(ping_fn, 5 * 1000); let handshake_start_msg; if (global.can_beam_out) { handshake_start_msg = MessageC2SHello.encode({ version: 2, requestedUsername: username, nextState: State.Play, user: window.localStorage.getItem("user")!, token: window.localStorage.getItem("token")! }).finish(); } else { handshake_start_msg = MessageC2SHello.encode({ version: 2, requestedUsername: username, nextState: State.Play, // @ts-ignore user: "",//null, // @ts-ignore token: ""//null }).finish(); } client.socket.send(encode(MessageC2SHello_packetInfo.type, handshake_start_msg)); client.socket.addEventListener('message', async (msg) => { let pkt_info = decode(new Uint8Array(await msg.data.arrayBuffer())); let pkt_id = pkt_info[0]; let pkt_data = pkt_info[1]; if (pkt_id == 0) { // not a real message, skip return; } if (client.state == State.Handshake) { if (pkt_id == MessageS2CHello_packetInfo.type) { let pkt = MessageS2CHello.decode(pkt_data); logger.info(`FAST CONNECT - Handshake finished with server with protocol v${pkt.version}, assigned username ${pkt.givenUsername}, switching to state ${pkt.nextState}`); client.state = pkt.nextState; client.username = pkt.givenUsername; client.version = pkt.version; } else if (pkt_id == MessageS2CGoodbye_packetInfo.type) { let pkt = MessageS2CGoodbye.decode(pkt_data); logger.error(`Disconnected by server. Reason: ${pkt.reason}`); client.state = State.Handshake; client.username = null; client.version = null; throw "Disconnected by server"; } else { logger.warn(`server sent unexpected packet ${pkt_id} for state Handshake`); } } else if (client.state == State.Play) { if (pkt_id == MessageS2CGoodbye_packetInfo.type) { let pkt = MessageS2CGoodbye.decode(pkt_data); logger.error(`Disconnected by server. Reason: ${pkt.reason}`); client.state = State.Handshake; client.username = null; client.version = null; throw "Disconnected by server"; } else if (pkt_id == MessageS2CChat_packetInfo.type) { let pkt = MessageS2CChat.decode(pkt_data); logger.info(`CHAT: [${pkt.from}] ${pkt.message}`); } else if (pkt_id == MessageS2CPong_packetInfo.type) { clearTimeout(client.ping_timeout_left); client.ping_timeout_left = setTimeout(ping_timeout_fn, 10 * 1000); } else if (pkt_id == MessageS2CPlayersUpdate_packetInfo.type) { let pkt = MessageS2CPlayersUpdate.decode(pkt_data); global.players = pkt.players; for (let i = 0; i < pkt.players.length; i++) { if (pkt.players[i].username == client.username) { if (global.me !== null) { let x_vel = (global.me.x - pkt.players[i].x) / (1 / 20); let y_vel = (global.me.y - pkt.players[i].y) / (1 / 20); let total_vel = Math.sqrt(x_vel * x_vel + y_vel * y_vel); global.velocity = total_vel; } global.me = pkt.players[i]; } } } else if (pkt_id == MessageS2CPlanetData_packetInfo.type) { let pkt = MessageS2CPlanetData.decode(pkt_data); global.planets = pkt.planets; } else if (pkt_id == MessageS2CModulesUpdate_packetInfo.type) { let pkt = MessageS2CModulesUpdate.decode(pkt_data); global.modules = pkt.modules; } else { logger.warn(`server sent unexpected packet ${pkt_id} for state Play`); } } }); return client; } let socket: WebSocket | undefined = undefined; function _websocket_connect(url: string): Promise { if (socket && socket.readyState < 2) { // reuse socket connection return Promise.resolve(socket); } return new Promise((resolve, reject) => { socket = new WebSocket(url); socket.onopen = () => { // @ts-ignore if here, guaranteed that `socket` != undefined resolve(socket); } socket.onerror = (err) => { reject(err); } }); }