From 707233a422df7ddc197dac5260627f3baaffc666 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 19 Apr 2019 22:12:33 +0900 Subject: [PATCH] Added null checks --- scripts/AnimeNotifier.ts | 67 ++++++++++++++++++-------- scripts/AudioPlayer.ts | 4 +- scripts/DateView.ts | 6 +-- scripts/PushManager.ts | 8 +-- scripts/ServerEvents.ts | 7 ++- scripts/ServiceWorker/ServiceWorker.ts | 25 ++++++---- scripts/ServiceWorkerManager.ts | 8 ++- scripts/SideBar.ts | 5 +- scripts/TouchController.ts | 8 +-- scripts/Utils/uploadWithProgress.ts | 6 ++- tsconfig.json | 1 + 11 files changed, 94 insertions(+), 51 deletions(-) diff --git a/scripts/AnimeNotifier.ts b/scripts/AnimeNotifier.ts index e1c37e40..75356998 100644 --- a/scripts/AnimeNotifier.ts +++ b/scripts/AnimeNotifier.ts @@ -18,7 +18,7 @@ import * as actions from "./Actions" export default class AnimeNotifier { app: Application analytics: Analytics - user: HTMLElement + user: HTMLElement | null title: string webpCheck: Promise webpEnabled: boolean @@ -110,12 +110,18 @@ export default class AnimeNotifier { run() { // Initiate the elements we need this.user = document.getElementById("user") - this.app.content = document.getElementById("content") - this.app.loading = document.getElementById("loading") + this.app.content = document.getElementById("content") as HTMLElement + this.app.loading = document.getElementById("loading") as HTMLElement // Theme - if(this.user && this.user.dataset.pro === "true" && this.user.dataset.theme !== "light") { - actions.applyTheme(this.user.dataset.theme) + if(this.user && this.user.dataset.pro === "true") { + const theme = this.user.dataset.theme + + // Don't apply light theme on load because + // it's already the standard theme. + if(theme && theme !== "light") { + actions.applyTheme(theme) + } } // Status message @@ -196,7 +202,7 @@ export default class AnimeNotifier { if(document.title !== this.title) { document.title = this.title } - } else { + } else if(headers[0].textContent) { document.title = headers[0].textContent } } @@ -289,7 +295,7 @@ export default class AnimeNotifier { } async onBeforeUnload(e: BeforeUnloadEvent) { - let message = undefined + let message = "" // Prevent closing tab on new thread page if(this.app.currentPath === "/new/thread" && document.activeElement.tagName === "TEXTAREA" && (document.activeElement as HTMLTextAreaElement).value.length > 20) { @@ -325,7 +331,14 @@ export default class AnimeNotifier { // Dynamic label assignment to prevent label texts overflowing // and taking horizontal space at page load. - element.dataset.label = element.getAttribute("aria-label") + let label = element.getAttribute("aria-label") + + if(!label) { + console.error("Tooltip without a label:", element) + return + } + + element.dataset.label = label // This is the most expensive call in this whole function, // it consumes about 2-4 ms every time you call it. @@ -350,7 +363,7 @@ export default class AnimeNotifier { let tipChild = document.createElement("div") tipChild.classList.add("tip-offset-child") - tipChild.setAttribute("data-label", element.getAttribute("data-label")) + tipChild.setAttribute("data-label", element.dataset.label) tipChild.style.left = Math.round(leftOffset) + "px" tipChild.style.width = rect.width + "px" tipChild.style.height = rect.height + "px" @@ -373,24 +386,25 @@ export default class AnimeNotifier { continue } - element.addEventListener("dragstart", e => { - if(!element.draggable) { + element.addEventListener("dragstart", evt => { + if(!element.draggable || !evt.dataTransfer) { return } let image = element.getElementsByClassName("anime-list-item-image")[0] if(image) { - e.dataTransfer.setDragImage(image, 0, 0) + evt.dataTransfer.setDragImage(image, 0, 0) } let name = element.getElementsByClassName("anime-list-item-name")[0] - e.dataTransfer.setData("text/plain", JSON.stringify({ + evt.dataTransfer.setData("text/plain", JSON.stringify({ api: element.dataset.api, animeTitle: name.textContent })) - e.dataTransfer.effectAllowed = "move" + + evt.dataTransfer.effectAllowed = "move" }, false) // Prevent re-attaching the same listeners @@ -404,7 +418,7 @@ export default class AnimeNotifier { } element.addEventListener("drop", async e => { - let toElement = e.toElement as HTMLElement + let toElement = e.toElement // Find tab element while(toElement && !toElement.classList.contains("tab")) { @@ -412,12 +426,12 @@ export default class AnimeNotifier { } // Ignore a drop on the current status tab - if(!toElement || toElement.classList.contains("active")) { + if(!toElement || toElement.classList.contains("active") || !e.dataTransfer) { return } let data = e.dataTransfer.getData("text/plain") - let json = null + let json: any try { json = JSON.parse(data) @@ -433,6 +447,11 @@ export default class AnimeNotifier { e.preventDefault() let tabText = toElement.textContent + + if(!tabText) { + return + } + let newStatus = tabText.toLowerCase() if(newStatus === "on hold") { @@ -477,7 +496,7 @@ export default class AnimeNotifier { } element.addEventListener("dragstart", e => { - if(!element.draggable) { + if(!element.draggable || !e.dataTransfer) { return } @@ -1236,8 +1255,10 @@ export default class AnimeNotifier { return element.dataset.api } - let apiObject: HTMLElement - let parent = element + let apiObject: HTMLElement | undefined + let parent: HTMLElement | null + + parent = element while(parent = parent.parentElement) { if(parent.dataset.api !== undefined) { @@ -1246,7 +1267,7 @@ export default class AnimeNotifier { } } - if(!apiObject) { + if(!apiObject || !apiObject.dataset.api) { this.statusMessage.showError("API object not found") throw "API object not found" } @@ -1269,6 +1290,10 @@ export default class AnimeNotifier { onKeyDown(e: KeyboardEvent) { let activeElement = document.activeElement + if(!activeElement) { + return + } + // Ignore hotkeys on input elements switch(activeElement.tagName) { case "INPUT": diff --git a/scripts/AudioPlayer.ts b/scripts/AudioPlayer.ts index c7f085c6..26c285b4 100644 --- a/scripts/AudioPlayer.ts +++ b/scripts/AudioPlayer.ts @@ -17,7 +17,7 @@ export default class AudioPlayer { playId = 0 // Save last request so that we can cancel it - lastRequest: XMLHttpRequest + lastRequest: XMLHttpRequest | null // DOM elements audioPlayer: HTMLElement @@ -194,7 +194,7 @@ export default class AudioPlayer { // Stop stop() { - this.arn.currentMediaId = undefined + this.arn.currentMediaId = "" // Remove CSS class "playing" let playingElements = document.getElementsByClassName("playing") diff --git a/scripts/DateView.ts b/scripts/DateView.ts index 49291f09..0959cc1f 100644 --- a/scripts/DateView.ts +++ b/scripts/DateView.ts @@ -60,7 +60,7 @@ function getRemainingTime(remaining: number): string { } export function displayAiringDate(element: HTMLElement, now: Date) { - if(element.dataset.startDate === "") { + if(!element.dataset.startDate || !element.dataset.endDate) { element.textContent = "" return } @@ -133,8 +133,8 @@ export function displayDate(element: HTMLElement, now: Date) { } } -export function displayTime(element: HTMLElement, now: Date) { - if(element.dataset.date === "") { +export function displayTime(element: HTMLElement) { + if(!element.dataset.date) { element.textContent = "" return } diff --git a/scripts/PushManager.ts b/scripts/PushManager.ts index 03a9c54c..4d7046ee 100644 --- a/scripts/PushManager.ts +++ b/scripts/PushManager.ts @@ -5,7 +5,7 @@ export default class PushManager { this.pushSupported = ("serviceWorker" in navigator) && ("PushManager" in window) } - async subscription(): Promise { + async subscription() { if(!this.pushSupported) { return Promise.resolve(null) } @@ -13,11 +13,7 @@ export default class PushManager { let registration = await navigator.serviceWorker.ready let subscription = await registration.pushManager.getSubscription() - if(subscription) { - return Promise.resolve(subscription) - } - - return Promise.resolve(null) + return Promise.resolve(subscription) } async subscribe(userId: string) { diff --git a/scripts/ServerEvents.ts b/scripts/ServerEvents.ts index 09e7dbac..43a3e8db 100644 --- a/scripts/ServerEvents.ts +++ b/scripts/ServerEvents.ts @@ -75,11 +75,16 @@ export default class ServerEvents { let button = document.getElementById("load-new-activities") - if(!button) { + if(!button || !button.dataset.count) { return } let buttonText = document.getElementById("load-new-activities-text") + + if(!buttonText) { + return + } + let newCount = parseInt(button.dataset.count) + 1 button.dataset.count = newCount.toString() buttonText.textContent = plural(newCount, "new activity") diff --git a/scripts/ServiceWorker/ServiceWorker.ts b/scripts/ServiceWorker/ServiceWorker.ts index 34d61474..0dff8369 100644 --- a/scripts/ServiceWorker/ServiceWorker.ts +++ b/scripts/ServiceWorker/ServiceWorker.ts @@ -99,12 +99,19 @@ function onActivate(evt: any) { // Only keep current version of the cache and delete old caches let cacheWhitelist = [cache.version] + // Query existing cache keys let deleteOldCache = caches.keys().then(keyList => { - return Promise.all(keyList.map(key => { + // Create a deletion for every key that's not whitelisted + let deletions = keyList.map(key => { if(cacheWhitelist.indexOf(key) === -1) { return caches.delete(key) } - })) + + return Promise.resolve(false) + }) + + // Wait for deletions + return Promise.all(deletions) }) // Immediate claim helps us gain control over a new client immediately @@ -334,7 +341,7 @@ function broadcast(msg: object) { } // installCache is called when the service worker is installed for the first time. -async function installCache() { +function installCache() { let urls = [ "/", "/scripts", @@ -343,19 +350,17 @@ async function installCache() { "https://media.notify.moe/images/elements/noise-strong.png", ] - let promises = [] - - for(let url of urls) { + let requests = urls.map(async url => { let request = new Request(url, { credentials: "same-origin", mode: "cors" }) - let promise = fetch(request).then(response => cache.store(request, response)) - promises.push(promise) - } + const response = await fetch(request) + await cache.store(request, response) + }) - return Promise.all(promises) + return Promise.all(requests) } // Serve network first. diff --git a/scripts/ServiceWorkerManager.ts b/scripts/ServiceWorkerManager.ts index 454481bf..8cd7a5ff 100644 --- a/scripts/ServiceWorkerManager.ts +++ b/scripts/ServiceWorkerManager.ts @@ -63,7 +63,13 @@ export default class ServiceWorkerManager { } postMessage(message: any) { - navigator.serviceWorker.controller.postMessage(JSON.stringify(message)) + const controller = navigator.serviceWorker.controller + + if(!controller) { + return + } + + controller.postMessage(JSON.stringify(message)) } onMessage(evt: MessageEvent) { diff --git a/scripts/SideBar.ts b/scripts/SideBar.ts index e1407acd..94683508 100644 --- a/scripts/SideBar.ts +++ b/scripts/SideBar.ts @@ -9,8 +9,9 @@ export default class SideBar { this.element = element document.body.addEventListener("click", e => { - if(document.activeElement.id === "search") - return; + if(document.activeElement && document.activeElement.id === "search") { + return + } this.hide() }) diff --git a/scripts/TouchController.ts b/scripts/TouchController.ts index 74accf8c..29d7bc89 100644 --- a/scripts/TouchController.ts +++ b/scripts/TouchController.ts @@ -15,6 +15,8 @@ export default class TouchController { this.downSwipe = this.upSwipe = this.rightSwipe = this.leftSwipe = () => null this.threshold = 3 + this.x = -1 + this.y = -1 } handleTouchStart(evt) { @@ -23,7 +25,7 @@ export default class TouchController { } handleTouchMove(evt) { - if(!this.x || !this.y) { + if(this.x === -1 || this.y === -1) { return } @@ -47,7 +49,7 @@ export default class TouchController { } } - this.x = undefined - this.y = undefined + this.x = -1 + this.y = -1 } } \ No newline at end of file diff --git a/scripts/Utils/uploadWithProgress.ts b/scripts/Utils/uploadWithProgress.ts index 45010c2a..22d32052 100644 --- a/scripts/Utils/uploadWithProgress.ts +++ b/scripts/Utils/uploadWithProgress.ts @@ -20,8 +20,10 @@ export function uploadWithProgress(url, options: RequestInit, onProgress: ((ev: xhr.open(options.method || "GET", url, true) - for(let k in options.headers || {}) { - xhr.setRequestHeader(k, options.headers[k]) + if(options.headers) { + for(let key in options.headers) { + xhr.setRequestHeader(key, options.headers[key]) + } } xhr.send(options.body) diff --git a/tsconfig.json b/tsconfig.json index 73e36d1d..0726cf13 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,6 +6,7 @@ "baseUrl": ".", "strict": false, "strictFunctionTypes": false, + "strictNullChecks": true, "noUnusedLocals": true, "noUnusedParameters": false }