Advertisement
Guest User

Untitled

a guest
Mar 24th, 2018
103
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 36.92 KB | None | 0 0
  1. index.html
  2. global.js
  3. sections/
  4. login/
  5. login.html
  6. login.js
  7. top_navbar/
  8. top_navbar.html
  9. server_interface/
  10. server_interface.js
  11. styles/
  12. cardshifter.css
  13. utils/
  14. formatDate.js
  15. loadHtml.js
  16. logDebugMessage.js
  17.  
  18. <!DOCTYPE html>
  19. <html>
  20. <head>
  21. <title>Cardshifter</title>
  22. <!-- Bootstrap -->
  23. <link href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.0/css/bootstrap.min.css" rel="stylesheet" />
  24.  
  25. <!-- Local styles -->
  26. <link rel="stylesheet" href="styles/cardshifter.css" />
  27.  
  28. <!-- Local JavaScript -->
  29. <script src="global.js"></script>
  30. <script src="server_interface/server_interface.js"></script>
  31. <script src="utils/loadHtml.js"></script>
  32. <script src="utils/formatDate.js"></script>
  33. <script src="utils/logDebugMessage.js"></script>
  34.  
  35. <!-- Local Section JavaScript -->
  36. <script src="sections/login/login.js"></script>
  37.  
  38. <!-- Favicon links -->
  39. <link rel="apple-touch-icon" sizes="57x57" href="images/favicon/apple-icon-57x57.png" />
  40. <link rel="apple-touch-icon" sizes="60x60" href="images/favicon/apple-icon-60x60.png" />
  41. <link rel="apple-touch-icon" sizes="72x72" href="images/favicon/apple-icon-72x72.png" />
  42. <link rel="apple-touch-icon" sizes="76x76" href="images/favicon/apple-icon-76x76.png" />
  43. <link rel="apple-touch-icon" sizes="114x114" href="images/favicon/apple-icon-114x114.png" />
  44. <link rel="apple-touch-icon" sizes="120x120" href="images/favicon/apple-icon-120x120.png" />
  45. <link rel="apple-touch-icon" sizes="144x144" href="images/favicon/apple-icon-144x144.png" />
  46. <link rel="apple-touch-icon" sizes="152x152" href="images/favicon/apple-icon-152x152.png" />
  47. <link rel="apple-touch-icon" sizes="180x180" href="images/favicon/apple-icon-180x180.png" />
  48. <link rel="icon" type="image/png" sizes="192x192" href="images/favicon/android-icon-192x192.png" />
  49. <link rel="icon" type="image/png" sizes="32x32" href="images/favicon/favicon-32x32.png" />
  50. <link rel="icon" type="image/png" sizes="96x96" href="images/favicon/favicon-96x96.png" />
  51. <link rel="icon" type="image/png" sizes="16x16" href="images/favicon/favicon-16x16.png" />
  52. <link rel="manifest" href="images/favicon/manifest.json" />
  53. <meta name="msapplication-TileColor" content="#ffffff" />
  54. <meta name="msapplication-TileImage" content="images/favicon/ms-icon-144x144.png" />
  55. <meta name="theme-color" content="#ffffff" />
  56. </head>
  57. <body>
  58. <!-- Top navigation bar -->
  59. <div id="top_navbar_container">
  60. <script>
  61. const navbarContainerId = "top_navbar_container";
  62. const navbarFilePath = "sections/top_navbar/top_navbar.html";
  63. loadHtml(navbarContainerId, navbarFilePath)
  64. .then(function() {
  65. if (DEBUG) {
  66. logDebugMessage(`"${navbarFilePath}" loaded OK!`);
  67. }
  68. });
  69. </script>
  70. </div>
  71.  
  72. <div class="csh-body">
  73. <div id="login_container">
  74. <script>
  75. const loginContainerId = "login_container";
  76. const loginFilePath = "sections/login/login.html";
  77. loadHtml(loginContainerId, loginFilePath)
  78. .then(function() {
  79. loginHandler();
  80. if (DEBUG) {
  81. logDebugMessage(`"${loginFilePath}" loaded OK!`);
  82. }
  83. });
  84. </script>
  85. </div>
  86. </div>
  87. </body>
  88. </html>
  89.  
  90. /*
  91. * This file is for global values to be used throughout the site.
  92. */
  93. 'use strict';
  94.  
  95. /*
  96. * Setting to `true` will log messages in the browser console
  97. * to help in debugging and keeping track of what is happening on the page.
  98. * This should be set to `false` on the public client.
  99. */
  100. const DEBUG = true;
  101.  
  102. /*
  103. * Port number used for WebSocket.
  104. */
  105. const WS_PORT = 4243;
  106.  
  107. /*
  108. * List of game server names and WebSocket URIs.
  109. */
  110. const GAME_SERVERS = {
  111. "localhost" : `ws://127.0.0.1:${WS_PORT}`,
  112. "dwarftowers.com" : `ws://dwarftowers.com:${WS_PORT}`,
  113. "zomis.net" : `ws://stats.zomis.net:${WS_PORT}`,
  114. "Other" : ""
  115. };
  116.  
  117. /**
  118. * Default date format for the application.
  119. * @type String
  120. */
  121. const DEFAULT_DATE_FORMAT = "yyyy/MM/dd hh:mm:ss";
  122.  
  123. <div id="login">
  124. <h4>Please log in to continue.</h4>
  125. <form name="login_form" id="login_form" class="login-form">
  126. <div id="login_server_select_container" class="form-group">
  127. <label for="login_server_list" aria-label="Server">Server:</label>
  128. <select name="login_server_list" id="login_server_list" class="form-control">
  129. </select>
  130. <div id="login_server_other_container" class="form-group" style="display : none">
  131. <label for="login_server_other_input">Other Server:</label>
  132. <input name="login_server_other_input" id="login_server_other_input" type="text" class="form-control" />
  133. <input type="button" name="test_login_server_other" id="test_login_server_other" class="btn" value="Test connection" />
  134. </div>
  135. <input readonly name="server_loading_display" id="server_connecting" class="form-control" style="background-color: #DDD; display: none" />
  136. <label for="login_secure">Is secure server:</label>
  137. <input name="login_secure" id="login_secure" type="checkbox" value="secure" />
  138. <span id="login_server_connection_status" class="label" style="display: block; text-align: left"></span>
  139. </div>
  140. <div id="login_username_container" class="form-group">
  141. <label for="login_username">Username:</label>
  142. <input name="login_username" id="login_username" type="text" class="form-control" placeholder="Enter name..." />
  143. </div>
  144. <div class="form-group">
  145. <input type="button" name="login_submit" id="login_submit" type="button" class="btn btn-success" value="Log in" />
  146. </div>
  147. </form>
  148. </div>
  149.  
  150. /* global GAME_SERVERS, DEBUG, CardshifterServerAPI, DEFAULT_DATE_FORMAT */
  151.  
  152. const loginHandler = function() {
  153. const serverSelectContainer = document.getElementById("login_server_select_container");
  154. const serverSelect = serverSelectContainer.querySelector("#login_server_list");
  155. const serverOtherInputContainer = serverSelectContainer.querySelector("#login_server_other_container");
  156. const serverLoading = serverSelectContainer.querySelector("#server_connecting");
  157. const connStatusMsg = serverSelectContainer.querySelector("#login_server_connection_status");
  158. let currentServerHasValidConnection = null;
  159.  
  160. /**
  161. * Adds options to the server selection based on GAME_SERVERS global.
  162. * @returns {undefined}
  163. */
  164. const populateServerSelect = function() {
  165. for (let key in GAME_SERVERS) {
  166. if (GAME_SERVERS.hasOwnProperty(key)) {
  167. const option = document.createElement("option");
  168. option.text = key;
  169. option.value = GAME_SERVERS[key];
  170. serverSelect.add(option);
  171. }
  172. }
  173. };
  174.  
  175. /**
  176. * Tests the WebSocket connection to a server and displays a message on the page
  177. * to give the user information about the connection status.
  178. * @returns {undefined}
  179. */
  180. const testWebsocketConnection = function() {
  181. const serverUri = serverSelect.value;
  182. const isSecure = false;
  183.  
  184. let msgText = "";
  185.  
  186. if (serverUri) {
  187. displayConnStatus("connecting", serverUri);
  188. /**
  189. * Test WebSocket connection and display status if successful.
  190. * @returns {undefined}
  191. */
  192. const onReady = function() {
  193. makeServerSelectReadWrite();
  194. msgText = displayConnStatus("success", serverUri);
  195. if (DEBUG) { logDebugMessage(msgText); }
  196. currentServerHasValidConnection = true;
  197. };
  198. /**
  199. * Test WebSocket connection and display status if failed.
  200. * @returns {undefined}
  201. */
  202. const onError = function() {
  203. makeServerSelectReadWrite();
  204. msgText = displayConnStatus("failure", serverUri);
  205. if (DEBUG) { logDebugMessage(msgText); }
  206. currentServerHasValidConnection = false;
  207. };
  208. CardshifterServerAPI.init(serverUri, isSecure, onReady, onError);
  209. makeServerSelectReadOnly(serverUri);
  210. }
  211. else {
  212. displayConnStatus("unknown", serverUri);
  213. }
  214. };
  215.  
  216. /**
  217. * Displays connection status in the page.
  218. * @param {string} status - Keyword representing the connection status
  219. * @param {type} serverUri - The URI of the server the client is connecting to
  220. * @returns {String} - The message text, largely for debug purposes
  221. */
  222. const displayConnStatus = function(status, serverUri) {
  223. let msgText = "";
  224. switch (status.toLowerCase()) {
  225. case "connecting":
  226. msgText =
  227. `<h5>Connecting to server...</h5>` +
  228. `<pre class='bg-warning'>` +
  229. `Address: ${serverUri}` +
  230. `n${formatDate(new Date())}` +
  231. `</pre>`;
  232. connStatusMsg.className = "label label-warning";
  233. connStatusMsg.innerHTML = msgText;
  234. break;
  235. case "success":
  236. msgText =
  237. `<h5>WebSocket connection OK.</h5>n` +
  238. `<pre class='bg-success'>`+
  239. `Address: ${serverUri}` +
  240. `n${formatDate(new Date())}` +
  241. `</pre>`;
  242. connStatusMsg.innerHTML = msgText;
  243. connStatusMsg.className = "label label-success";
  244. break;
  245. case "failure":
  246. msgText =
  247. `<h5>WebSocket connection FAILED.</h5>n` +
  248. `<pre class='bg-danger'>`+
  249. `Address: ${serverUri}` +
  250. `n${formatDate(new Date())}` +
  251. `</pre>`;
  252. connStatusMsg.innerHTML = msgText;
  253. connStatusMsg.className = "label label-danger";
  254. break;
  255. case "unknown":
  256. default:
  257. msgText = `<h5>Unknown connection status...</h5>`;
  258. connStatusMsg.innerHTML = msgText;
  259. connStatusMsg.className = "label label-default";
  260. break;
  261. }
  262. return msgText;
  263. };
  264.  
  265. /**
  266. * Hides the `select` element and shows a read-only `input` instead.
  267. * @param {string} serverUri
  268. * @returns {undefined}
  269. */
  270. const makeServerSelectReadOnly = function(serverUri) {
  271. const selector = document.getElementById("login_server_list");
  272. const connecting = document.getElementById("server_connecting");
  273. selector.style.display = "none";
  274. connecting.style.display = "block";
  275. connecting.value = `Connecting to ${serverUri}...`;
  276. };
  277.  
  278. /**
  279. * Makes the server `select` element visible and hides the read-only `input`
  280. * @returns {undefined}
  281. */
  282. const makeServerSelectReadWrite = function() {
  283. const selector = document.getElementById("login_server_list");
  284. const connecting = document.getElementById("server_connecting");
  285. selector.style.display = "block";
  286. connecting.style.display = "none";
  287. };
  288.  
  289. /**
  290. * Displays an input field for server address if "Other" server is selected.
  291. * @returns {undefined}
  292. */
  293. const handleServerSelectChanges = function() {
  294. if (serverSelect.value) {
  295. serverOtherInputContainer.style.display = "none";
  296. }
  297. else {
  298. serverOtherInputContainer.style.display = "block";
  299. }
  300. };
  301.  
  302. /**
  303. * Attempts to login to game server.
  304. * @returns {undefined}
  305. */
  306. const tryLogin = function() {
  307. const username = document.getElementById("login_username").value;
  308. if (!username) {
  309. displayNoUsernameWarning();
  310. }
  311. else {
  312. const isSecure = false;
  313. var loggedIn = null;
  314.  
  315. let serverUri = serverSelect.value;
  316. if (!serverUri) {
  317. serverUri = document.getElementById("login_server_other_input").value;
  318. }
  319.  
  320.  
  321.  
  322. /**
  323. * Short-circuit login attempt if we've already found that the connection not valid.
  324. * @type String
  325. */
  326. if (!currentServerHasValidConnection) {
  327. const msg = "Websocket error(error 1)";
  328. console.log(msg);
  329. displayLoginFailureWarning(msg);
  330. }
  331.  
  332. /**
  333. * Attempt to log in once the WebSocket connection is ready.
  334. * @returns {undefined}
  335. */
  336. const onReady = function() {
  337. let login = new CardshifterServerAPI.messageTypes.LoginMessage(username);
  338.  
  339. /**
  340. * Listens for a welcome message from the game server, and stores user values in the browser.
  341. * @param {Object} welcome
  342. * @returns {undefined}
  343. */
  344. const messageListener = function(welcome) {
  345. const SUCCESS = 200;
  346. const SUCCESS_MESSAGE = "OK";
  347. if(welcome.status === SUCCESS && welcome.message === SUCCESS_MESSAGE) {
  348. localStorage.setItem("username", username);
  349. localStorage.setItem("id", welcome.userId);
  350. localStorage.setItem("playerIndex", null);
  351. localStorage.setItem("game", { "id" : null, "mod" : null });
  352. }
  353. else {
  354. console.log(`${new Date()} server message: ${welcome.message}`);
  355. loggedIn = false;
  356. }
  357. };
  358.  
  359. try {
  360. CardshifterServerAPI.setMessageListener(messageListener, ["loginresponse"]);
  361. CardshifterServerAPI.sendMessage(login);
  362. }
  363. catch(error) {
  364. const msg = "LoginMessage error(error 2)";
  365. if (DEBUG) { logDebugMessage(`${msg} ${error}`); }
  366. displayLoginFailureWarning(msg, error);
  367. loggedIn = false;
  368. }
  369. };
  370.  
  371. /**
  372. * Log error if the connection fails
  373. * @returns {undefined}
  374. */
  375. const onError = function() {
  376. const msg = "Websocket error(error 1)";
  377. if (DEBUG) { logDebugMessage(msg); }
  378. displayLoginFailureWarning(msg);
  379. loggedIn = false;
  380. };
  381.  
  382. CardshifterServerAPI.init(serverUri, isSecure, onReady, onError);
  383. }
  384. };
  385.  
  386.  
  387.  
  388. /**
  389. * Displays a warning if no username is entered.
  390. * @returns {undefined}
  391. */
  392. const displayNoUsernameWarning = function() {
  393. const container = document.getElementById("login_username_container");
  394. if (!container.querySelector("#login_username_missing_msg")) {
  395. const msg = document.createElement("span");
  396. msg.id = "login_username_missing_msg";
  397. msg.className = "label label-danger";
  398. msg.innerHTML = "Please enter a username.";
  399. container.appendChild(msg);
  400. }
  401. };
  402.  
  403. const displayLoginFailureWarning = function(message, error) {
  404. const container = document.getElementById("login_username_container");
  405. const warning = document.createElement("span");
  406. warning.id = "login_failure_msg";
  407. warning.className = "label label-danger";
  408. warning.style = "display: block; text-align: left;";
  409. warning.innerHTML = `<h5>Login failed: ${message}</h5>`;
  410. if (error) {
  411. warning.innerHTML += `<pre>${error}</pre>`;
  412. }
  413. container.appendChild(warning);
  414. };
  415.  
  416. const testOtherServerConnection = function() {
  417. const otherServerInput = document.getElementById("login_server_other_input");
  418. const otherServerUri = otherServerInput.value;
  419. const isSecure = false;
  420.  
  421. /**
  422. * Test WebSocket connection and display status if successful.
  423. * @returns {undefined}
  424. */
  425. const onReady = function() {
  426. makeServerSelectReadWrite();
  427. msgText = displayConnStatus("success", otherServerUri);
  428. if (DEBUG) { logDebugMessage(msgText); }
  429. currentServerHasValidConnection = true;
  430. };
  431. /**
  432. * Test WebSocket connection and display status if failed.
  433. * @returns {undefined}
  434. */
  435. const onError = function() {
  436. makeServerSelectReadWrite();
  437. msgText = displayConnStatus("failure", otherServerUri);
  438. if (DEBUG) { logDebugMessage(msgText); }
  439. currentServerHasValidConnection = false;
  440. };
  441. CardshifterServerAPI.init(otherServerUri, isSecure, onReady, onError);
  442. makeServerSelectReadOnly();
  443. displayConnStatus("connecting", otherServerUri);
  444. };
  445.  
  446. /**
  447. * IIFE to setup the login handling for the page it is loaded in.
  448. * @type undefined
  449. */
  450. const runLoginHandler = function() {
  451. populateServerSelect();
  452. document.getElementById("login_server_list").addEventListener("change", handleServerSelectChanges, false);
  453. document.getElementById("login_server_list").addEventListener("change", testWebsocketConnection, false);
  454. document.getElementById("login_submit").addEventListener("click", tryLogin, false);
  455. document.getElementById("test_login_server_other").addEventListener("click", testOtherServerConnection, false);
  456. testWebsocketConnection();
  457. }();
  458. };
  459.  
  460. <nav id="top_navbar" class="navbar navbar-inverse">
  461. <div class="container-fluid">
  462.  
  463. <div class="navbar-header">
  464. <!-- TODO fix this logic -->
  465. <div class="navbar-brand csh-top-link">Cardshifter</div>
  466. </div>
  467.  
  468. <form class="navbar-form">
  469.  
  470. <ul class ="navbar-form navbar-left" style="margin-top: 8px;">
  471. <li class="dropdown">
  472. <a href="#" class="dropdown-toggle csh-dropdown-link" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
  473. Mods
  474. <span class="caret"></span></a>
  475. <ul class="dropdown-menu">
  476. <li class="cyborg-font">Cyborg Chronicles</li>
  477. <li class="cyborg-font"><a href=#>Game rules</a></li>
  478. <li class="cyborg-font"><a href=#>Cards</a></li>
  479.  
  480. <li role="separator" class="divider"></li>
  481.  
  482. <li class="mythos-font">Mythos</li>
  483. <li class="mythos-font"><a href=#>Game rules</a></li>
  484. <li class="mythos-font"><a href=#>Cards</a></li>
  485. </ul>
  486. </li>
  487. </ul>
  488. <ul class ="navbar-form navbar-left" style="margin-top: 8px;">
  489. <li class="dropdown">
  490. <a href="#" class="dropdown-toggle csh-dropdown-link" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
  491. Help
  492. <span class="caret"></span></a>
  493. </li>
  494. </ul>
  495. <ul class ="navbar-form navbar-left" style="margin-top: 8px;">
  496. <li class="dropdown">
  497. <a href="#" class="dropdown-toggle csh-dropdown-link" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
  498. About
  499. <span class="caret"></span></a>
  500. </li>
  501. </ul>
  502.  
  503. <div class="form-group navbar-form navbar-left">
  504. <input name="disconnect_websocket" id="disconnect_websocket" type="button" value="Log Out" class="btn btn-navbar csh-button" />
  505. </div>
  506. <div class="form-group navbar-form navbar-left">
  507. <input name="display_console" id="display_console" type="button" value="Console" class="btn btn-navbar csh-button" />
  508. </div>
  509.  
  510. </form>
  511.  
  512. </div>
  513. </nav>
  514.  
  515. "use strict";
  516.  
  517. // checks if the string begins with either ws:// or wss://
  518. const wsProtocolFinder = /ws(s)*:///;
  519.  
  520. /*
  521. * Enum for WebSocket ready state constants.
  522. * @enum {number}
  523. */
  524. const readyStates = {
  525. CONNECTING : 0,
  526. OPEN : 1,
  527. CLOSING : 2,
  528. CLOSED : 3
  529. };
  530.  
  531. const MAIN_LOBBY = 1;
  532.  
  533. let eventTypes = [];
  534.  
  535. /**
  536. * The base class Message for all the other message types
  537. * to inherit from.
  538. *
  539. * TODO: Would it just be easier to set the `.command` property
  540. * individually for each card type?
  541. *
  542. * @param {string} command - The command of the message.
  543. */
  544. const Message = function(command) {
  545. this.command = command;
  546. };
  547.  
  548. /**
  549. * The exception that is thrown when the code is trying to
  550. * interact with the API when the API has not been
  551. * initialized with `.init` yet.
  552. *
  553. * @param {string} message - Informational message about the exception.
  554. */
  555. const NotInitializedException = function(message) {
  556. this.name = "NotInitializedException";
  557. this.message = message || "";
  558. };
  559.  
  560. /**
  561. * The exception that is thrown when the code is telling the
  562. * API to interact with the socket when the socket is not
  563. * ready to accept any information.
  564. *
  565. * @param {string} message - Informational message about the exception.
  566. * @param {number} readyState - Ready state constant from WebSocket API, https://developer.mozilla.org/en-US/docs/Web/API/WebSocket
  567. */
  568. const SocketNotReadyException = function(message, readyState) {
  569. this.name = "SocketNotReadyException";
  570. this.message = message || "";
  571. this.readyState = readyState;
  572. };
  573.  
  574. /*
  575. * Returns all the keys of an object and its inherited keys.
  576. * This is used so `JSON.stringify` can get the `.command` of a message.
  577. *
  578. * @param {Object} obj - The object to flatten
  579. * @return {Object} - a new Object, containing obj's keys and inherited keys
  580. * @source http://stackoverflow.com/questions/8779249/how-to-stringify-inherited-objects-to-json
  581. */
  582. const flatten = function(obj) {
  583. let result = Object.create(obj);
  584. for(let key in result) {
  585. // TODO this assignment is weird, why is `result[key]` being assigned to its own value?
  586. result[key] = result[key];
  587. }
  588. return result;
  589. };
  590.  
  591. /*
  592. * Singleton object to handle communication via WebSocket between the client
  593. * and the game server.
  594. */
  595. const CardshifterServerAPI = {
  596. socket: null,
  597. messageTypes: {
  598. /*
  599. * Incoming login message.
  600. * A login message from a client to add a user to the available users on the server.
  601. * This login message is required before any other action or message can be performed between a client and a server.
  602. * @constructor
  603. * @param {string} username - The incoming user name passed from client to server, not null
  604. * @example Message: <code>{ "command":"login","username":"JohnDoe" }</code>
  605. */
  606. LoginMessage : function(username) {
  607. this.username = username;
  608. },
  609.  
  610. /*
  611. * Request available targets for a specific action to be performed by an entity.
  612. * These in-game messages request a list of all available targets for a given action and entity.
  613. * The client uses this request in order to point out targets (hopefully with a visual aid such as highlighting targets)
  614. * that an entity (such as a creature card, or a player) can perform an action on (for example attack or enchant a card).
  615. * @constructor
  616. * @param {number} gameId - The Id of this game currently being played
  617. * @param {number} id - The Id of this entity which requests to perform an action
  618. * @param {string} action - The name of this action requested to be performed
  619. */
  620. RequestTargetsMessage : function(gameId, id, action) {
  621. this.gameId = gameId;
  622. this.id = id;
  623. this.action = action;
  624. },
  625.  
  626. /*
  627. * Make a specific type of request to the server.
  628. * This is used to request an action from the server which requires server-side information.
  629. * @constructor
  630. * @param {string} request - This request
  631. * @param {string} message - The message accompanying this request
  632. */
  633. ServerQueryMessage : function(request, message) {
  634. this.request = request;
  635. this.message = message;
  636.  
  637. this.toString = function() {
  638. return `ServerQueryMessage: Request${this.request} message: ${this.message}`;
  639. };
  640. },
  641.  
  642.  
  643. /*
  644. * Request to start a new game.
  645. * This is sent from the Client to the Server when this player invites another player (including AI)
  646. * to start a new game of a chosen type.
  647. * @constructor
  648. * @param opponent - The Id of the player entity being invited by this player
  649. * @param gameType - The type / mod of the game chosen by this player
  650. */
  651. StartGameRequest : function(opponent, gameType) {
  652. this.opponent = opponent;
  653. this.gameType = gameType;
  654. },
  655.  
  656. /*
  657. * Serialize message from JSON to byte.
  658. * Primarily used for libGDX client.
  659. * Constructor.
  660. * @param type - This message type
  661. */
  662. TransformerMessage : function(type) {
  663. this.type = type;
  664. },
  665.  
  666. /*
  667. * Message for a game entity to use a certain ability.
  668. * Game entities (e.g., cards, players) may have one or more ability actions that they can perform.
  669. * Certain abilities can have multiple targets, hence the use of an array.
  670. * @constructor
  671. * Used for multiple target actions.
  672. *
  673. * @param gameId - This current game
  674. * @param entity - This game entity performing an action
  675. * @param action - This action
  676. * @param targets - The set of multiple targets affected by this action
  677. */
  678. UseAbilityMessage : function(gameId, id, action, targets) {
  679. this.gameId = gameId;
  680. this.id = id;
  681. this.action = action;
  682. this.targets = targets;
  683.  
  684. this.toString = function() {
  685. return ``
  686. + `UseAbilityMessage`
  687. + `[id=${this.id},`
  688. + `action=${this.action},`
  689. + `gameId=${this.gameId}`
  690. + `targets=${this.targets.toString()}]`
  691. ;
  692. };
  693. },
  694.  
  695. /*
  696. * Chat message in game lobby.
  697. * These are messages printed to the game lobby which are visible to all users present at the time the message is posted.
  698. * @constructor
  699. * @param {string} message - The content of this chat message
  700. */
  701. ChatMessage : function(message) {
  702. this.chatId = MAIN_LOBBY;
  703. this.message = message;
  704.  
  705. this.toString = function() {
  706. // TODO where does that `from` param/var come from?
  707. return `ChatMessage [chatId=${chatId}, message=${message}, from=${from}]`;
  708. };
  709. },
  710.  
  711. /*
  712. * Request to invite a player to start a new game.
  713. * @constructor
  714. * @param id - The Id of this invite request
  715. * @param {string} name - The name of the player being invited
  716. * @param gameType - The game type of this invite request
  717. */
  718. InviteRequest : function(id, name, gameType) {
  719. this.id = id;
  720. this.name = name;
  721. this.gameType = gameType;
  722. },
  723.  
  724. /*
  725. * Response to an InviteRequest message.
  726. * @constructor
  727. * @param inviteId - Id of this incoming InviteRequest message
  728. * @param {boolean} accepted - Whether or not the InviteRequest is accepted
  729. */
  730. InviteResponse : function(inviteId, accepted) {
  731. this.inviteId = inviteId;
  732. this.accepted = accepted;
  733. },
  734.  
  735. /*
  736. * Player configuration for a given game.
  737. * @constructor
  738. * @param gameId - This game
  739. * @param {string} modName - The mod name for this game
  740. * @param {Map} configs - Map of player name and applicable player configuration
  741. */
  742. PlayerConfigMessage : function(gameId, modName, configs) {
  743. this.gameId = gameId;
  744. this.modName = modName;
  745. this.configs = configs;
  746.  
  747. this.toString = function() {
  748. return ``
  749. + `PlayerConfigMessage{`
  750. + `configs=${configs}, `
  751. + `gameId=${gameId}, `
  752. + `modName='${modName}'`
  753. + `}`
  754. ;
  755. };
  756. }
  757. },
  758. /*
  759. * Initializes the API for use.
  760. *
  761. * This sets up all the message types to inherit the main `Message` class, and sets
  762. * up the websocket that will be used to communicate to the server, and to recieve
  763. * information from the server.
  764. *
  765. * @param {string} server - The server address to connect to
  766. * @param {boolean} isSecure - Whether to use SSL for the connection (NOT IMPLEMENTED)
  767. * @param onReady - Function to assign to `socket.onopen`
  768. * @param onError - Function to assign to `socket.onerror`
  769. */
  770. init : function(server, isSecure, onReady, onError) {
  771. let types = this.messageTypes;
  772. // TODO find out why this unused variable is here
  773. let self = this; // for the events
  774.  
  775. types.LoginMessage.prototype = new Message("login");
  776. types.RequestTargetsMessage.prototype = new Message("requestTargets");
  777. types.ServerQueryMessage.prototype = new Message("query");
  778. types.StartGameRequest.prototype = new Message("startgame");
  779. types.TransformerMessage.prototype = new Message("serial");
  780. types.UseAbilityMessage.prototype = new Message("use");
  781. types.ChatMessage.prototype = new Message("chat");
  782. types.InviteRequest.prototype = new Message("inviteRequest");
  783. types.InviteResponse.prototype = new Message("inviteResponse");
  784. types.PlayerConfigMessage.prototype = new Message("playerconfig");
  785. NotInitializedException.prototype = new Error();
  786. SocketNotReadyException.prototype = new Error();
  787.  
  788. // secure websocket is wss://, rather than ws://
  789. const secureAddon = (isSecure ? "s" : "");
  790. // if the protocol is not found in the string, store the correct protocol (is secure?)
  791. const protocolAddon = (wsProtocolFinder.test(server) ? "" : `ws${secureAddon}://`);
  792.  
  793. let socket = new WebSocket(protocolAddon + server);
  794.  
  795. socket.onopen = onReady;
  796.  
  797. socket.onerror = function() {
  798. onError();
  799. this.socket = null;
  800. };
  801.  
  802. this.socket = socket;
  803. },
  804.  
  805. /**
  806. * Sends a message to the server
  807. *
  808. * @param {Object} message - The message to send
  809. * @error SocketNotReadyException - The socket is not ready to be used
  810. * @error NotInitializedException - The API has not yet been initialized
  811. */
  812. sendMessage : function(message) {
  813. const socket = this.socket;
  814. // TODO find out why this unused variable is here
  815. let self = this;
  816. if (socket) {
  817. if (socket.readyState === readyStates.OPEN) {
  818. this.socket.send(JSON.stringify(flatten(message)));
  819. }
  820. else {
  821. throw new SocketNotReadyException("The Websocket is not ready to be used.", socket.readyState);
  822. }
  823. }
  824. else {
  825. throw new NotInitializedException("The API has not yet been initialized.");
  826. }
  827. },
  828.  
  829. /**
  830. * Sets an event listener for when the server sends a message and
  831. * the message type is one of the types in types
  832. *
  833. * @param listener - The function to fire when a message of types is received
  834. * @param {string[]} types - (OPTIONAL) Only fire the listener when the message type is in this array
  835. * @param {Object} timeout - (OPTIONAL) The function(.ontimeout) to call after MS(.ms) of no reply
  836. *
  837. * TODO: Maybe a timeout will be needed? Pass in a function and a MS count.
  838. */
  839. setMessageListener : function(listener, types, timeout) {
  840. eventTypes = types;
  841.  
  842. this.socket.onmessage = function(message) {
  843. var data = JSON.parse(message.data);
  844. if (eventTypes) {
  845. if(eventTypes.indexOf(data.command) !== -1) { // if contains
  846. listener(data);
  847. }
  848. }
  849. else {
  850. listener(data);
  851. }
  852. };
  853. },
  854.  
  855. /**
  856. * Adds types to the types to listen for in the message event listener
  857. *
  858. * @param {string[]} types - The types to add
  859. */
  860. addEventTypes : function(types) {
  861. eventTypes = eventTypes.concat(types);
  862. },
  863.  
  864. /**
  865. * Removes the message event listener
  866. */
  867. removeMessageListener : function() {
  868. this.socket.onmessage = null;
  869. }
  870. };
  871.  
  872. /* global DEFAULT_DATE_FORMAT */
  873.  
  874. /**
  875. * Formats a Date object based on a format string, e.g., "yyyy/MM/dd hh:MM:ss"
  876. * Original source:
  877. * https://dzone.com/articles/javascript-formatdate-function
  878. * Original source modified to fix a few bugs and modernize.
  879. *
  880. * @param {Date} date - the Date to format
  881. * @param {String} formatString - the format string to use
  882. * @returns {String} - the formatted date
  883. */
  884. const formatDate = function (date, formatString=DEFAULT_DATE_FORMAT) {
  885. if(date instanceof Date) {
  886. const months = new Array("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec");
  887. const yyyy = date.getFullYear();
  888. const yy = yyyy.toString().slice(-2);
  889. const M = date.getMonth() + 1;
  890. const MM = M < 10 ? `0${M}` : M;
  891. const MMM = months[M - 1];
  892. const d = date.getDate();
  893. const dd = d < 10 ? `0${d}` : d;
  894. const h = date.getHours();
  895. const hh = h < 10 ? `0${h}` : h;
  896. const m = date.getMinutes();
  897. const mm = m < 10 ? `0${m}` : m;
  898. const s = date.getSeconds();
  899. const ss = s < 10 ? `0${s}` : s;
  900. formatString = formatString.replace(/yyyy/, yyyy);
  901. formatString = formatString.replace(/yy/, yy);
  902. formatString = formatString.replace(/MMM/, MMM);
  903. formatString = formatString.replace(/MM/, MM);
  904. formatString = formatString.replace(/M/, M);
  905. formatString = formatString.replace(/dd/, dd);
  906. formatString = formatString.replace(/d/, d);
  907. formatString = formatString.replace(/hh/, hh);
  908. formatString = formatString.replace(/h/, h);
  909. formatString = formatString.replace(/mm/, mm);
  910. formatString = formatString.replace(/m/, m);
  911. formatString = formatString.replace(/ss/, ss);
  912. formatString = formatString.replace(/s/, s);
  913. return formatString;
  914. } else {
  915. return "";
  916. }
  917. };
  918.  
  919. /* global fetch, DEBUG */
  920. "use strict";
  921.  
  922. /*
  923. * Replicates the functionality of jQuery's `load` function,
  924. * used to load some HTML from another file into the current one.
  925. *
  926. * Based on this Stack Overflow answer:
  927. * https://stackoverflow.com/a/38132775/3626537
  928. * And `fetch` documentation:
  929. * https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch
  930. *
  931. * @param {string} parentElementId - The ID of the DOM element to load into
  932. * @param {string} htmlFilePath - The path of the HTML file to load
  933. */
  934. const loadHtml = function (parentElementId, filePath) {
  935. const init = {
  936. method: "GET",
  937. headers: { "Content-Type": "text/html" },
  938. mode: "cors",
  939. cache: "default"
  940. };
  941. // Return Promise from `fetch` allows to use `.then` after call.
  942. return fetch(filePath, init)
  943. .then(function (response) {
  944. return response.text();
  945. })
  946. .then(function (body) {
  947. // Replace `#` char in case the function gets called `querySelector` or jQuery style
  948. if (parentElementId.startsWith("#")) {
  949. parentElementId.replace("#", "");
  950. }
  951. document.getElementById(parentElementId).innerHTML = body;
  952. if (DEBUG) {
  953. console.log(`File "${filePath}" loaded into element ID "${parentElementId}"`);
  954. }
  955. })
  956. .catch(function(err) {
  957. throw new FailureToLoadHTMLException(
  958. `Could not load "${filePath} ` +
  959. `into element ID "${parentElementId}"` +
  960. `n${err}`
  961. );
  962. });
  963. };
  964.  
  965. const FailureToLoadHTMLException = function(message) {
  966. this.name = "FailureToLoadHTMLException";
  967. this.message = message;
  968. this.stack = (new Error()).stack;
  969. };
  970.  
  971. FailureToLoadHTMLException.prototype = new Error;
  972.  
  973. /* global DEFAULT_DATE_FORMAT */
  974.  
  975. /**
  976. * Log a debug message to the browser's JavaScript console.
  977. * @param {String} msg
  978. * @param {String} dateFormat
  979. * @returns {undefined}
  980. */
  981. const logDebugMessage = function(msg, dateFormat=DEFAULT_DATE_FORMAT) {
  982. const timestamp = new Date();
  983. console.log(`DEBUG | ${formatDate(timestamp, dateFormat)} | ${msg}`);
  984. };
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement