diff --git a/src/common/util/promise-timeout.ts b/src/common/util/promise-timeout.ts new file mode 100644 index 0000000000..b26e8ba4b8 --- /dev/null +++ b/src/common/util/promise-timeout.ts @@ -0,0 +1,10 @@ +export const promiseTimeout = (ms: number, promise: Promise) => { + const timeout = new Promise((_resolve, reject) => { + setTimeout(() => { + reject(`Timed out in ${ms} ms.`); + }, ms); + }); + + // Returns a race between our timeout and the passed in promise + return Promise.race([promise, timeout]); +}; diff --git a/src/components/ha-icon.ts b/src/components/ha-icon.ts index 73563d26f6..7fea1da222 100644 --- a/src/components/ha-icon.ts +++ b/src/components/ha-icon.ts @@ -125,6 +125,7 @@ export class HaIcon extends LitElement { databaseIcon = await getIcon(iconName); } catch (_err) { // Firefox in private mode doesn't support IDB + // iOS Safari sometimes doesn't open the DB databaseIcon = undefined; } diff --git a/src/data/iconsets.ts b/src/data/iconsets.ts index 8b92ddbfa3..debfcea7e2 100644 --- a/src/data/iconsets.ts +++ b/src/data/iconsets.ts @@ -1,4 +1,5 @@ -import { clear, get, set, createStore } from "idb-keyval"; +import { clear, get, set, createStore, promisifyRequest } from "idb-keyval"; +import { promiseTimeout } from "../common/util/promise-timeout"; import { iconMetadata } from "../resources/icon-metadata"; import { IconMeta } from "../types"; @@ -14,33 +15,34 @@ export const iconStore = createStore("hass-icon-db", "mdi-icon-store"); export const MDI_PREFIXES = ["mdi", "hass", "hassio", "hademo"]; -let toRead: Array<[string, (iconPath: string) => void, () => void]> = []; +let toRead: Array< + [string, (iconPath: string | undefined) => void, (e: any) => void] +> = []; // Queue up as many icon fetches in 1 transaction export const getIcon = (iconName: string) => - new Promise((resolve, reject) => { + new Promise((resolve, reject) => { toRead.push([iconName, resolve, reject]); if (toRead.length > 1) { return; } - const results: Array<[(iconPath: string) => void, IDBRequest]> = []; - - iconStore("readonly", (store) => { - for (const [iconName_, resolve_] of toRead) { - results.push([resolve_, store.get(iconName_)]); - } - }) - .then(() => { - for (const [resolve_, request] of results) { - resolve_(request.result); + promiseTimeout( + 1000, + iconStore("readonly", (store) => { + for (const [iconName_, resolve_, reject_] of toRead) { + promisifyRequest(store.get(iconName_)) + .then((icon) => resolve_(icon)) + .catch((e) => reject_(e)); } }) - .catch(() => { + ) + .catch((e) => { // Firefox in private mode doesn't support IDB + // Safari sometime doesn't open the DB so we time out for (const [, , reject_] of toRead) { - reject_(); + reject_(e); } }) .finally(() => {