diff options
Diffstat (limited to 'webAO')
| -rw-r--r-- | webAO/client.js | 267 | ||||
| -rw-r--r-- | webAO/encoding.js | 2 | ||||
| -rw-r--r-- | webAO/images/logo-new.png | bin | 29302 -> 28737 bytes | |||
| -rw-r--r-- | webAO/index.html | 13 | ||||
| -rw-r--r-- | webAO/logo-new-512.png | bin | 0 -> 238578 bytes | |||
| -rw-r--r-- | webAO/logo-new.png | bin | 0 -> 29302 bytes | |||
| -rw-r--r-- | webAO/manifest.json | 23 | ||||
| -rw-r--r-- | webAO/master.js | 34 | ||||
| -rw-r--r-- | webAO/services/request.js | 32 | ||||
| -rw-r--r-- | webAO/sw.js | 26 | ||||
| -rw-r--r-- | webAO/utils/calculateGifLength.js | 27 | ||||
| -rw-r--r-- | webAO/utils/calculateWebpLength.js | 22 | ||||
| -rw-r--r-- | webAO/utils/calculatorHandler.js | 7 | ||||
| -rw-r--r-- | webAO/utils/getCookie.js | 26 | ||||
| -rw-r--r-- | webAO/utils/setCookie.js | 10 |
15 files changed, 312 insertions, 177 deletions
diff --git a/webAO/client.js b/webAO/client.js index c742b20..ed3302a 100644 --- a/webAO/client.js +++ b/webAO/client.js @@ -4,21 +4,25 @@ * credits to aleks for original idea and source */ -import FingerprintJS from '@fingerprintjs/fingerprintjs' +import FingerprintJS from '@fingerprintjs/fingerprintjs'; import { EventEmitter } from 'events'; import { - escapeChat, encodeChat, prepChat, safe_tags, + escapeChat, encodeChat, prepChat, safeTags, } from './encoding.js'; // Load some defaults for the background and evidence dropdowns -import vanilla_character_arr from "./constants/characters.js"; -import vanilla_music_arr from "./constants/music.js"; -import vanilla_background_arr from "./constants/backgrounds.js"; -import vanilla_evidence_arr from "./constants/evidence.js"; +import vanilla_character_arr from './constants/characters.js'; +import vanilla_music_arr from './constants/music.js'; +import vanilla_background_arr from './constants/backgrounds.js'; +import vanilla_evidence_arr from './constants/evidence.js'; import chatbox_arr from './styles/chatbox/chatboxes.js'; import iniParse from './iniParse'; +import calculatorHandler from './utils/calculatorHandler.js'; +import getCookie from './utils/getCookie.js'; +import setCookie from './utils/setCookie.js'; +import request from './services/request.js'; const version = process.env.npm_package_version; @@ -31,8 +35,7 @@ location.search.substr(1).split('&').forEach((item) => { queryDict[item.split('=')[0]] = item.split('=')[1]; }); -const serverIP = queryDict.ip; -let { mode } = queryDict; +let { ip: serverIP, mode } = queryDict; // Unless there is an asset URL specified, use the wasabi one const DEFAULT_HOST = 'http://attorneyoffline.de/base/'; @@ -50,39 +53,35 @@ const transparentPNG = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCA let oldLoading = false; // presettings -const selectedEffect = 0; let selectedMenu = 1; let selectedShout = 0; let extrafeatures = []; let hdid; -const options = { fonts: { extendedJsFonts: true, userDefinedFonts: ['Ace Attorney', '8bitoperator', 'DINEngschrift'] }, excludes: { userAgent: true, enumerateDevices: true } }; function isLowMemory() { if (/webOS|iPod|BlackBerry|BB|PlayBook|IEMobile|Windows Phone|Kindle|Silk|PlayStation|Nintendo|Opera Mini/i.test(navigator.userAgent)) { oldLoading = true; } } - -const fpPromise = FingerprintJS.load() +const fpPromise = FingerprintJS.load(); fpPromise - .then(fp => fp.get()) - .then(result => { - hdid = result.visitorId; - client = new Client(serverIP); - viewport = new Viewport(); - - isLowMemory(); - client.loadResources(); -}); + .then((fp) => fp.get()) + .then((result) => { + hdid = result.visitorId; + client = new Client(serverIP); + viewport = new Viewport(); + + isLowMemory(); + client.loadResources(); + }); let lastICMessageTime = new Date(0); class Client extends EventEmitter { constructor(address) { super(); - console.log(`mode: ${mode}`); if (mode !== 'replay') { this.serv = new WebSocket(`ws://${address}`); // Assign the websocket events @@ -610,7 +609,7 @@ class Client extends EventEmitter { document.getElementById('client_inner_chat').innerHTML = ''; const char_id = Number(args[9]); - const char_name = safe_tags(args[3]); + const char_name = safeTags(args[3]); let msg_nameplate = args[3]; let msg_blips = 'male'; @@ -638,21 +637,21 @@ class Client extends EventEmitter { if (char_muted === false) { let chatmsg = { - deskmod: safe_tags(args[1]).toLowerCase(), - preanim: safe_tags(args[2]).toLowerCase(), // get preanim + deskmod: safeTags(args[1]).toLowerCase(), + preanim: safeTags(args[2]).toLowerCase(), // get preanim nameplate: msg_nameplate, chatbox: char_chatbox, name: char_name, - sprite: safe_tags(args[4]).toLowerCase(), + sprite: safeTags(args[4]).toLowerCase(), content: prepChat(args[5]), // Escape HTML tags side: args[6].toLowerCase(), - sound: safe_tags(args[7]).toLowerCase(), - blips: safe_tags(msg_blips), + sound: safeTags(args[7]).toLowerCase(), + blips: safeTags(msg_blips), type: Number(args[8]), charid: char_id, snddelay: Number(args[10]), objection: Number(args[11]), - evidence: safe_tags(args[12]), + evidence: safeTags(args[12]), flip: Number(args[13]), flash: Number(args[14]), color: Number(args[15]), @@ -660,10 +659,10 @@ class Client extends EventEmitter { if (extrafeatures.includes('cccc_ic_support')) { const extra_cccc = { - showname: safe_tags(args[16]), + showname: safeTags(args[16]), other_charid: Number(args[17]), - other_name: safe_tags(args[18]), - other_emote: safe_tags(args[19]), + other_name: safeTags(args[18]), + other_emote: safeTags(args[19]), self_offset: args[20].split('<and>'), // HACK: here as well, client is fucked and uses this instead of & other_offset: args[21].split('<and>'), other_flip: Number(args[22]), @@ -675,9 +674,9 @@ class Client extends EventEmitter { const extra_27 = { looping_sfx: Number(args[24]), screenshake: Number(args[25]), - frame_screenshake: safe_tags(args[26]), - frame_realization: safe_tags(args[27]), - frame_sfx: safe_tags(args[28]), + frame_screenshake: safeTags(args[26]), + frame_realization: safeTags(args[27]), + frame_sfx: safeTags(args[28]), }; chatmsg = Object.assign(extra_27, chatmsg); @@ -841,9 +840,9 @@ class Client extends EventEmitter { } const mute_select = document.getElementById('mute_select'); - mute_select.add(new Option(safe_tags(chargs[0]), charid)); + mute_select.add(new Option(safeTags(chargs[0]), charid)); const pair_select = document.getElementById('pair_select'); - pair_select.add(new Option(safe_tags(chargs[0]), charid)); + pair_select.add(new Option(safeTags(chargs[0]), charid)); // sometimes ini files lack important settings const default_options = { @@ -863,13 +862,13 @@ class Client extends EventEmitter { cini.emotions = Object.assign(default_emotions, cini.emotions); this.chars[charid] = { - name: safe_tags(chargs[0]), - showname: safe_tags(cini.options.showname), - desc: safe_tags(chargs[1]), - blips: safe_tags(cini.options.blips).toLowerCase(), - gender: safe_tags(cini.options.gender).toLowerCase(), - side: safe_tags(cini.options.side).toLowerCase(), - chat: (cini.options.chat === '') ? safe_tags(cini.options.chat).toLowerCase() : safe_tags(cini.options.category).toLowerCase(), + name: safeTags(chargs[0]), + showname: safeTags(cini.options.showname), + desc: safeTags(chargs[1]), + blips: safeTags(cini.options.blips).toLowerCase(), + gender: safeTags(cini.options.gender).toLowerCase(), + side: safeTags(cini.options.side).toLowerCase(), + chat: (cini.options.chat === '') ? safeTags(cini.options.chat).toLowerCase() : safeTags(cini.options.category).toLowerCase(), evidence: chargs[3], icon, inifile: cini, @@ -879,7 +878,7 @@ class Client extends EventEmitter { if (this.chars[charid].blips === '') { this.chars[charid].blips = this.chars[charid].gender; } const iniedit_select = document.getElementById('client_ininame'); - iniedit_select.add(new Option(safe_tags(chargs[0]))); + iniedit_select.add(new Option(safeTags(chargs[0]))); } else { console.warn(`missing charid ${charid}`); const img = document.getElementById(`demo_${charid}`); @@ -949,7 +948,7 @@ class Client extends EventEmitter { this.evidences[i - 1] = { name: prepChat(arg[0]), desc: prepChat(arg[1]), - filename: safe_tags(arg[2]), + filename: safeTags(arg[2]), icon: `${AO_HOST}evidence/${encodeURI(arg[2].toLowerCase())}`, }; } @@ -1032,15 +1031,8 @@ class Client extends EventEmitter { } isAudio(trackname) { - if (trackname.endsWith('.wav') - || trackname.endsWith('.mp3') - || trackname.endsWith('.mp4') - || trackname.endsWith('.ogg') - || trackname.endsWith('.opus')) // NOT category markers - { - return true; - } - return false; + const audioEndings = ['.wav', '.mp3', '.ogg', '.opus']; + return audioEndings.filter((ending) => trackname.endsWith(ending)).length === 1; } addTrack(trackname) { @@ -1105,7 +1097,7 @@ class Client extends EventEmitter { for (let i = 2; i < args.length - 1; i++) { if (i % 2 === 0) { document.getElementById('client_loadingtext').innerHTML = `Loading Music ${args[1]}/${this.music_list_length}`; - const trackname = safe_tags(args[i]); + const trackname = safeTags(args[i]); const trackindex = args[i - 1]; if (this.musics_time) { this.addTrack(trackname); @@ -1136,7 +1128,7 @@ class Client extends EventEmitter { for (let i = 1; i < args.length - 1; i++) { // Check when found the song for the first time - const trackname = safe_tags(args[i]); + const trackname = safeTags(args[i]); const trackindex = i - 1; document.getElementById('client_loadingtext').innerHTML = `Loading Music ${i}/${this.music_list_length}`; if (this.musics_time) { @@ -1163,7 +1155,7 @@ class Client extends EventEmitter { for (let i = 1; i < args.length - 1; i++) { // Check when found the song for the first time - this.addTrack(safe_tags(args[i])); + this.addTrack(safeTags(args[i])); } } @@ -1175,7 +1167,7 @@ class Client extends EventEmitter { this.resetAreaList(); for (let i = 1; i < args.length - 1; i++) { - this.createArea(i - 1, safe_tags(args[i])); + this.createArea(i - 1, safeTags(args[i])); } } @@ -1204,7 +1196,7 @@ class Client extends EventEmitter { * @param {Array} args kick reason */ handleKK(args) { - this.handleBans('Kicked', safe_tags(args[1])); + this.handleBans('Kicked', safeTags(args[1])); } /** @@ -1213,7 +1205,7 @@ class Client extends EventEmitter { * @param {Array} args ban reason */ handleKB(args) { - this.handleBans('Banned', safe_tags(args[1])); + this.handleBans('Banned', safeTags(args[1])); this.banned = true; } @@ -1223,7 +1215,7 @@ class Client extends EventEmitter { * @param {Array} args ban reason */ handleBB(args) { - alert(safe_tags(args[1])); + alert(safeTags(args[1])); } /** @@ -1232,7 +1224,7 @@ class Client extends EventEmitter { * @param {Array} args ban reason */ handleBD(args) { - this.handleBans('Banned', safe_tags(args[1])); + this.handleBans('Banned', safeTags(args[1])); this.banned = true; } @@ -1256,7 +1248,7 @@ class Client extends EventEmitter { * @param {Array} args packet arguments */ handleBN(args) { - viewport.bgname = safe_tags(args[1]); + viewport.bgname = safeTags(args[1]); const bgfolder = viewport.bgFolder; const bg_index = getIndexFromSelect('bg_select', viewport.bgname); document.getElementById('bg_select').selectedIndex = bg_index; @@ -1427,13 +1419,13 @@ class Client extends EventEmitter { this.areas[i].players = Number(args[i + 1]); break; case 1: // status - this.areas[i].status = safe_tags(args[i + 1]); + this.areas[i].status = safeTags(args[i + 1]); break; case 2: - this.areas[i].cm = safe_tags(args[i + 1]); + this.areas[i].cm = safeTags(args[i + 1]); break; case 3: - this.areas[i].locked = safe_tags(args[i + 1]); + this.areas[i].locked = safeTags(args[i + 1]); break; } @@ -1613,7 +1605,7 @@ class Client extends EventEmitter { iniedit_select.innerHTML = ''; function addIniswap(value) { - iniedit_select.add(new Option(safe_tags(value))); + iniedit_select.add(new Option(safeTags(value))); } addIniswap(me.name); @@ -1942,13 +1934,19 @@ class Viewport { * silently fail and return 0 instead. * @param {string} filename the animation file name */ - async getAnimLength(filename) { - try { - const file = await requestBuffer(filename); - return this.calculateGifLength(file); - } catch (err) { - return 0; + + async getAnimLength(url) { + const extensions = ['.gif', '.webp']; + for (const extension of extensions) { + const urlWithExtension = url + extension; + const exists = await fileExists(urlWithExtension); + if (exists) { + const fileBuffer = await requestBuffer(urlWithExtension); + const length = calculatorHandler[extension](fileBuffer); + return length; + } } + return 0; } oneSuccess(promises) { @@ -2052,19 +2050,30 @@ class Viewport { const png_s = document.getElementById(`client_${position}${pairID}_png`); const apng_s = document.getElementById(`client_${position}${pairID}_apng`); const webp_s = document.getElementById(`client_${position}${pairID}_webp`); + const extensionsMap = { + '.gif': gif_s, + '.png': png_s, + '.apng': apng_s, + '.webp': webp_s, + }; - if (this.lastChar !== this.chatmsg.name) { - // hide the last sprite - gif_s.src = transparentPNG; - png_s.src = transparentPNG; - apng_s.src = transparentPNG; - webp_s.src = transparentPNG; + for (const [extension, htmlElement] of Object.entries(extensionsMap)) { + // Hides all sprites before creating a new sprite + if (this.lastChar !== this.chatmsg.name) { + htmlElement.src = transparentPNG; + } + let url; + if (extension === '.png') { + url = `${characterFolder}${encodeURI(charactername)}/${encodeURI(emotename)}${extension}`; + } else { + url = `${characterFolder}${encodeURI(charactername)}/${encodeURI(prefix)}${encodeURI(emotename)}${extension}`; + } + const exists = fileExistsSync(url); + if (exists) { + htmlElement.src = url; + return; + } } - - gif_s.src = `${characterFolder}${encodeURI(charactername)}/${encodeURI(prefix)}${encodeURI(emotename)}.gif`; - png_s.src = `${characterFolder}${encodeURI(charactername)}/${encodeURI(emotename)}.png`; - apng_s.src = `${characterFolder}${encodeURI(charactername)}/${encodeURI(prefix)}${encodeURI(emotename)}.apng`; - webp_s.src = `${characterFolder}${encodeURI(charactername)}/${encodeURI(prefix)}${encodeURI(emotename)}.webp`; } /** @@ -2167,7 +2176,7 @@ class Viewport { // Hide message box chatContainerBox.style.opacity = 0; // If preanim existed then determine the length - gifLength = await this.getAnimLength(`${AO_HOST}characters/${encodeURI(this.chatmsg.name.toLowerCase())}/${encodeURI(this.chatmsg.preanim)}.gif`); + gifLength = await this.getAnimLength(`${AO_HOST}characters/${encodeURI(this.chatmsg.name.toLowerCase())}/${encodeURI(this.chatmsg.preanim)}`); this.chatmsg.startspeaking = false; break; // case 5: @@ -2325,9 +2334,10 @@ class Viewport { this.chatmsg.startspeaking = true; } else if (this.textTimer >= this.shoutTimer + this.chatmsg.preanimdelay && !this.chatmsg.startpreanim) { if (this.chatmsg.startspeaking) { + // Evidence Bullshit if (this.chatmsg.evidence > 0) { // Prepare evidence - eviBox.src = safe_tags(client.evidences[this.chatmsg.evidence - 1].icon); + eviBox.src = safeTags(client.evidences[this.chatmsg.evidence - 1].icon); eviBox.style.width = 'auto'; eviBox.style.height = '36.5%'; @@ -2411,42 +2421,6 @@ class Viewport { } /** - * read a cookie from storage - * got this from w3schools - * https://www.w3schools.com/js/js_cookies.asp - * @param {String} cname The name of the cookie to return - */ -function getCookie(cname) { - try { - const name = `${cname}=`; - const decodedCookie = decodeURIComponent(document.cookie); - const ca = decodedCookie.split(';'); - for (let i = 0; i < ca.length; i++) { - let c = ca[i]; - while (c.charAt(0) === ' ') { - c = c.substring(1); - } - if (c.indexOf(name) === 0) { - return c.substring(name.length, c.length); - } - } - return ''; - } catch (error) { - return ''; - } -} - -/** - * set a cookie - * the version from w3schools expects these to expire - * @param {String} cname The name of the cookie to return - * @param {String} value The value of that cookie option - */ -function setCookie(cname, value) { - document.cookie = `${cname}=${value}`; -} - -/** * Triggered when the Return key is pressed on the out-of-character chat input box. * @param {KeyboardEvent} event */ @@ -2731,7 +2705,7 @@ window.iniedit = iniedit; export async function switchPanTilt(addcheck) { const background = document.getElementById('client_fullview'); if (addcheck === 1) { - document.getElementById('client_pantilt').checked = true; + document.getElementById('client_pantilt').checked = true; document.getElementById('client_court').style.display = ''; } else if (addcheck === 2) { document.getElementById('client_pantilt').checked = false; @@ -2813,7 +2787,6 @@ window.imgError = imgError; * @param {HTMLImageElement} image the element containing the missing sound */ export function opusCheck(channel) { - console.info(channel); console.info(`failed to load sound ${channel.src}`); let oldsrc = ''; oldsrc = channel.src; @@ -2860,40 +2833,6 @@ async function requestBuffer(url) { } /** - * Make a GET request for a specific URI. - * @param {string} url the URI to be requested - * @returns response data - * @throws {Error} if status code is not 2xx, or a network error occurs - */ -async function request(url) { - return new Promise((resolve, reject) => { - const xhr = new XMLHttpRequest(); - xhr.responseType = 'text'; - xhr.addEventListener('error', () => { - const err = new Error(`Request for ${url} failed: ${xhr.statusText}`); - err.code = xhr.status; - reject(err); - }); - xhr.addEventListener('abort', () => { - const err = new Error(`Request for ${url} was aborted!`); - err.code = xhr.status; - reject(err); - }); - xhr.addEventListener('load', () => { - if (xhr.status < 200 || xhr.status >= 300) { - const err = new Error(`Request for ${url} failed with status code ${xhr.status}`); - err.code = xhr.status; - reject(err); - } else { - resolve(xhr.response); - } - }); - xhr.open('GET', url, true); - xhr.send(); - }); -} - -/** * Checks if a file exists at the specified URI. * @param {string} url the URI to be checked */ @@ -2905,6 +2844,16 @@ async function fileExists(url) { return false; } } +const fileExistsSync = (url) => { + try { + const http = new XMLHttpRequest(); + http.open('HEAD', url, false); + http.send(); + return http.status != 404; + } catch (e) { + return false; + } +}; /** * Triggered when the reconnect button is pushed. diff --git a/webAO/encoding.js b/webAO/encoding.js index 21b5aa7..e6cc3ae 100644 --- a/webAO/encoding.js +++ b/webAO/encoding.js @@ -28,7 +28,7 @@ export function unescapeChat(estring) { * XXX: This is unnecessary if we use `createTextNode` instead! * @param {string} unsafe an unsanitized string */ -export function safe_tags(unsafe) { +export function safeTags(unsafe) { if (unsafe) { return unsafe .replace(/>/g, '>') diff --git a/webAO/images/logo-new.png b/webAO/images/logo-new.png Binary files differindex f53fe30..26e04aa 100644 --- a/webAO/images/logo-new.png +++ b/webAO/images/logo-new.png diff --git a/webAO/index.html b/webAO/index.html index d3fc05f..c82cacf 100644 --- a/webAO/index.html +++ b/webAO/index.html @@ -26,6 +26,7 @@ content="default-src 'self' 'unsafe-inline' 'unsafe-eval' *.aceattorneyonline.com data:; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; connect-src 'self' ws:;"> <meta name="viewport" content="width=700, initial-scale=1"> + <meta name="theme-color" content="#2c3d51"> <link href="https://fonts.googleapis.com/css?family=Poiret+One" rel="stylesheet"> <link href="https://fonts.googleapis.com/css?family=Oswald%7CRoboto+Condensed" rel="stylesheet"> @@ -35,6 +36,18 @@ <link href="vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet"> <link rel="icon" href="images/favicon.ico"> <script src="master.b.js"></script> + <link rel="manifest" href="manifest.json"> + <script> + if ('serviceWorker' in navigator) { + window.addEventListener('load', () => { + navigator.serviceWorker.register('../sw.js').then( () => { + console.log('Service Worker Registered') + }) + }) +} + </script> + + </head> <body> diff --git a/webAO/logo-new-512.png b/webAO/logo-new-512.png Binary files differnew file mode 100644 index 0000000..33fd586 --- /dev/null +++ b/webAO/logo-new-512.png diff --git a/webAO/logo-new.png b/webAO/logo-new.png Binary files differnew file mode 100644 index 0000000..f53fe30 --- /dev/null +++ b/webAO/logo-new.png diff --git a/webAO/manifest.json b/webAO/manifest.json new file mode 100644 index 0000000..acda5d5 --- /dev/null +++ b/webAO/manifest.json @@ -0,0 +1,23 @@ + { + "name": "Ace Attorney Online", + "short_name": "WebAO", + "start_url": "index.html", + "scope": "./", + "icons": [ + { + "src": "/logo-new.png", + "sizes": "128x128", + "type": "image/png" + }, + { + "src": "/logo-new-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "any maskable" + + } + ], + "theme_color": "#2c3d51", + "background_color": "#2c3d51", + "display": "standalone" + }
\ No newline at end of file diff --git a/webAO/master.js b/webAO/master.js index 4ff6402..7e4906e 100644 --- a/webAO/master.js +++ b/webAO/master.js @@ -1,6 +1,6 @@ -import FingerprintJS from '@fingerprintjs/fingerprintjs' +import FingerprintJS from '@fingerprintjs/fingerprintjs'; -import { unescapeChat, safe_tags } from './encoding.js'; +import { unescapeChat, safeTags } from './encoding.js'; const version = process.env.npm_package_version; @@ -21,22 +21,22 @@ servers[-1] = { name: 'Localhost', description: 'This is your computer on port 50001', ip: '127.0.0.1', port: 50001, assets: '', online: 'Online: ?/?', }; -const fpPromise = FingerprintJS.load() +const fpPromise = FingerprintJS.load(); fpPromise - .then(fp => fp.get()) - .then(result => { - hdid = result.visitorId; + .then((fp) => fp.get()) + .then((result) => { + hdid = result.visitorId; - check_https(); + check_https(); - masterserver = new WebSocket(`ws://${MASTERSERVER_IP}`); - masterserver.onopen = (evt) => onOpen(evt); - masterserver.onerror = (evt) => onError(evt); - masterserver.onmessage = (evt) => onMessage(evt); + masterserver = new WebSocket(`ws://${MASTERSERVER_IP}`); + masterserver.onopen = (evt) => onOpen(evt); + masterserver.onerror = (evt) => onError(evt); + masterserver.onmessage = (evt) => onMessage(evt); - // i don't need the ms to play alone - setTimeout(() => checkOnline(-1, '127.0.0.1:50001'), 0); -}); + // i don't need the ms to play alone + setTimeout(() => checkOnline(-1, '127.0.0.1:50001'), 0); + }); export function check_https() { if (document.location.protocol === 'https:') { @@ -50,7 +50,7 @@ export function setServ(ID) { if (document.getElementById(`server${ID}`).className === '') { checkOnline(ID, `${servers[ID].ip}:${servers[ID].port}`); } if (servers[ID].description !== undefined) { - document.getElementById('serverdescription_content').innerHTML = `<b>${servers[ID].online}</b><br>${safe_tags(servers[ID].description)}`; + document.getElementById('serverdescription_content').innerHTML = `<b>${servers[ID].online}</b><br>${safeTags(servers[ID].description)}`; } else { document.getElementById('serverdescription_content').innerHTML = ''; } @@ -107,7 +107,7 @@ function checkOnline(serverID, coIP) { oserv.close(); return; } - if (serverID === selectedServer) { document.getElementById('serverdescription_content').innerHTML = `<b>${servers[serverID].online}</b><br>${safe_tags(servers[serverID].description)}`; } + if (serverID === selectedServer) { document.getElementById('serverdescription_content').innerHTML = `<b>${servers[serverID].online}</b><br>${safeTags(servers[serverID].description)}`; } } // assign the callbacks @@ -145,7 +145,7 @@ function onMessage(e) { const asset = args[4] ? `&asset=${args[4]}` : ''; document.getElementById('masterlist').innerHTML - += `<li id="server${i}" onmouseover="setServ(${i})"><p>${safe_tags(servers[i].name)}</p>` + += `<li id="server${i}" onmouseover="setServ(${i})"><p>${safeTags(servers[i].name)}</p>` + `<a class="button" href="client.html?mode=watch&ip=${ipport}${asset}">Watch</a>` + `<a class="button" href="client.html?mode=join&ip=${ipport}${asset}">Join</a></li>`; } diff --git a/webAO/services/request.js b/webAO/services/request.js new file mode 100644 index 0000000..6479dcc --- /dev/null +++ b/webAO/services/request.js @@ -0,0 +1,32 @@ +/** + * Make a GET request for a specific URI. + * @param {string} url the URI to be requested + * @returns response data + * @throws {Error} if status code is not 2xx, or a network error occurs + */ +const request = async (url) => new Promise((resolve, reject) => { + const xhr = new XMLHttpRequest(); + xhr.responseType = 'text'; + xhr.addEventListener('error', () => { + const err = new Error(`Request for ${url} failed: ${xhr.statusText}`); + err.code = xhr.status; + reject(err); + }); + xhr.addEventListener('abort', () => { + const err = new Error(`Request for ${url} was aborted!`); + err.code = xhr.status; + reject(err); + }); + xhr.addEventListener('load', () => { + if (xhr.status < 200 || xhr.status >= 300) { + const err = new Error(`Request for ${url} failed with status code ${xhr.status}`); + err.code = xhr.status; + reject(err); + } else { + resolve(xhr.response); + } + }); + xhr.open('GET', url, true); + xhr.send(); +}); +export default request; diff --git a/webAO/sw.js b/webAO/sw.js new file mode 100644 index 0000000..09a2251 --- /dev/null +++ b/webAO/sw.js @@ -0,0 +1,26 @@ +const cacheName = 'webAO'; + +// Cache all the files to make a PWA +self.addEventListener('install', (e) => { + e.waitUntil( + caches.open(cacheName).then((cache) => + // Our application only has two files here index.html and manifest.json + // but you can add more such as style.css as your app grows + cache.addAll([ + './', + './index.html', + '../manifest.json', + ])), + ); +}); + +// Our service worker will intercept all fetch requests +// and check if we have cached the file +// if so it will serve the cached file +self.addEventListener('fetch', (event) => { + event.respondWith( + caches.open(cacheName) + .then((cache) => cache.match(event.request, { ignoreSearch: true })) + .then((response) => response || fetch(event.request)), + ); +}); diff --git a/webAO/utils/calculateGifLength.js b/webAO/utils/calculateGifLength.js new file mode 100644 index 0000000..1df0ba9 --- /dev/null +++ b/webAO/utils/calculateGifLength.js @@ -0,0 +1,27 @@ +/** + * Adds up the frame delays to find out how long a GIF is + * I totally didn't steal this + * @param {data} gifFile the GIF data + */ +const calculateGifLength = (gifFile) => { + const d = new Uint8Array(gifFile); + // Thanks to http://justinsomnia.org/2006/10/gif-animation-duration-calculation/ + // And http://www.w3.org/Graphics/GIF/spec-gif89a.txt + let duration = 0; + for (let i = 0; i < d.length; i++) { + // Find a Graphic Control Extension hex(21F904__ ____ __00) + if (d[i] === 0x21 + && d[i + 1] === 0xF9 + && d[i + 2] === 0x04 + && d[i + 7] === 0x00) { + // Swap 5th and 6th bytes to get the delay per frame + const delay = (d[i + 5] << 8) | (d[i + 4] & 0xFF); + + // Should be aware browsers have a minimum frame delay + // e.g. 6ms for IE, 2ms modern browsers (50fps) + duration += delay < 2 ? 10 : delay; + } + } + return duration * 10; +}; +export default calculateGifLength; diff --git a/webAO/utils/calculateWebpLength.js b/webAO/utils/calculateWebpLength.js new file mode 100644 index 0000000..1b422a0 --- /dev/null +++ b/webAO/utils/calculateWebpLength.js @@ -0,0 +1,22 @@ +const calculateWebpLength = (webpFile) => { + const d = new Uint8Array(webpFile); + // https://developers.google.com/speed/webp/docs/riff_container#animation + let duration = 0; + for (let i = 0; i < d.length; i++) { + // Find ANMF header (41 4E 4D 46) + if (d[i] === 0x41 + && d[i + 1] === 0x4E + && d[i + 2] === 0x4D + && d[i + 3] === 0x46) { + // Swap 5th and 6th bytes to get the delay per frame + const delay = (d[i + 21] << 8) | (d[i + 20] & 0xFF); + + // Should be aware browsers have a minimum frame delay + // e.g. 6ms for IE, 2ms modern browsers (50fps) + duration += delay < 2 ? 10 : delay; + } + } + return duration; +}; + +export default calculateWebpLength; diff --git a/webAO/utils/calculatorHandler.js b/webAO/utils/calculatorHandler.js new file mode 100644 index 0000000..7686573 --- /dev/null +++ b/webAO/utils/calculatorHandler.js @@ -0,0 +1,7 @@ +import calculateGifLength from './calculateGifLength'; +import calculateWebpLength from './calculateWebpLength'; + +export default { + '.gif': calculateGifLength, + '.webp': calculateWebpLength, +}; diff --git a/webAO/utils/getCookie.js b/webAO/utils/getCookie.js new file mode 100644 index 0000000..3be0733 --- /dev/null +++ b/webAO/utils/getCookie.js @@ -0,0 +1,26 @@ +/** + * read a cookie from storage + * got this from w3schools + * https://www.w3schools.com/js/js_cookies.asp + * @param {String} cname The name of the cookie to return + */ +const getCookie = (cname) => { + try { + const name = `${cname}=`; + const decodedCookie = decodeURIComponent(document.cookie); + const ca = decodedCookie.split(';'); + for (let i = 0; i < ca.length; i++) { + let c = ca[i]; + while (c.charAt(0) === ' ') { + c = c.substring(1); + } + if (c.indexOf(name) === 0) { + return c.substring(name.length, c.length); + } + } + return ''; + } catch (error) { + return ''; + } +}; +export default getCookie; diff --git a/webAO/utils/setCookie.js b/webAO/utils/setCookie.js new file mode 100644 index 0000000..9734eae --- /dev/null +++ b/webAO/utils/setCookie.js @@ -0,0 +1,10 @@ +/** + * set a cookie + * the version from w3schools expects these to expire + * @param {String} cname The name of the cookie to return + * @param {String} value The value of that cookie option + */ +const setCookie = (cname, value) => { + document.cookie = `${cname}=${value}`; +}; +export default setCookie; |
