From 74bafae8d6d7451d8e8723cf2286539868029393 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20Westk=C3=A4mper?= Date: Mon, 4 Mar 2024 16:52:38 +0200 Subject: [PATCH 1/2] Fix type assertions --- .eslintrc.json | 3 ++- src/api/client.ts | 14 +++++++------- src/search.ts | 4 ++-- src/shims.d.ts | 12 ++++++++++++ src/utils/dom.ts | 9 +++------ src/utils/dropdown.ts | 2 +- src/utils/ga.ts | 45 ++++++++++++++++--------------------------- src/utils/history.ts | 2 +- src/utils/limiter.ts | 7 ++++--- src/utils/state.ts | 15 +++++++++------ 10 files changed, 58 insertions(+), 55 deletions(-) create mode 100644 src/shims.d.ts diff --git a/.eslintrc.json b/.eslintrc.json index 3236794..1456c0c 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -18,6 +18,7 @@ "promise" ], "rules": { - "promise/prefer-await-to-then": "error" + "promise/prefer-await-to-then": "error", + "@typescript-eslint/consistent-type-assertions": ["error", { "assertionStyle": "never" }] } } diff --git a/src/api/client.ts b/src/api/client.ts index 75aa8bc..9eca23a 100644 --- a/src/api/client.ts +++ b/src/api/client.ts @@ -96,25 +96,25 @@ export function getNostoClient(): PromiseLike { * @param error Error instance to log * @param level Log level string */ -export function log(message: string, error: unknown, level: LogLevel): void +export async function log(message: string, error: unknown, level: LogLevel): Promise /** * * @param error Error instance to log * @param level Log level string */ -export function log(error: unknown, level: LogLevel): void -export function log( +export async function log(error: unknown, level: LogLevel): Promise +export async function log( msgOrError: unknown, errorOrLevel: unknown, optLevel?: LogLevel ) { const msg = typeof msgOrError === "string" ? msgOrError : undefined const error = optLevel ? errorOrLevel : !msg ? msgOrError : undefined - const level = (optLevel || (typeof errorOrLevel === "string" ? errorOrLevel : "error")) as LogLevel + // @ts-expect-error type mismatch + const level: LogLevel = (optLevel || (typeof errorOrLevel === "string" ? errorOrLevel : "error")) if (error) { - getNostoClient().then(api => { - api.captureError(error, "nostoAutocomplete", level) - }) + const api = await getNostoClient() + api.captureError(error, "nostoAutocomplete", level) } console[level](...[msg, error].filter(Boolean)) } diff --git a/src/search.ts b/src/search.ts index 8dfc18f..036d607 100644 --- a/src/search.ts +++ b/src/search.ts @@ -78,7 +78,7 @@ const defaultProductFields = [ * }) * ``` */ -export async function search( +export async function search( query: InputSearchQueryWithFields, options?: SearchOptions ) { @@ -102,5 +102,5 @@ export async function search( }, { redirect, track }) - return { query, response } as State + return { query, response } } diff --git a/src/shims.d.ts b/src/shims.d.ts new file mode 100644 index 0000000..42e67c5 --- /dev/null +++ b/src/shims.d.ts @@ -0,0 +1,12 @@ +type GaNamespace = { + (): void + getAll(): { + send(type: string, url: string): void + }[] +} + +interface Window { + ga?: GaNamespace + gtag?: unknown + google_tag_manager?: unknown +} \ No newline at end of file diff --git a/src/utils/dom.ts b/src/utils/dom.ts index bd885a8..fb8f13e 100644 --- a/src/utils/dom.ts +++ b/src/utils/dom.ts @@ -12,7 +12,7 @@ export function findAll( } else if (selector instanceof Array) { return selector } else if (selector instanceof NodeList) { - return Array.prototype.slice.call(selector) as Element[] + return Array.prototype.slice.call(selector) } return Array.prototype.slice.call(document.querySelectorAll(selector)) })() @@ -48,14 +48,11 @@ export function parents(target: Selector, selector?: string): Element[] { ) } -type WithMsMatchesSelector = { - msMatchesSelector?: typeof Element.prototype.matches -} - export function matches(target: Selector, selector: string): boolean { const matchesFunc = Element.prototype.matches || - (Element.prototype as WithMsMatchesSelector).msMatchesSelector || + // @ts-expect-error proprietary method + Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector return findAll(target).some(element => matchesFunc.call(element, selector)) } diff --git a/src/utils/dropdown.ts b/src/utils/dropdown.ts index ff7b605..6e1f13f 100644 --- a/src/utils/dropdown.ts +++ b/src/utils/dropdown.ts @@ -229,7 +229,7 @@ export function createDropdown( async function init() { const state = await Promise.resolve(initialState) - await Promise.resolve(render(container, state as State)) + await Promise.resolve(render(container, state)) // Without setTimeout React does not have committed DOM changes yet, so we don't have the correct elements. setTimeout(() => { diff --git a/src/utils/ga.ts b/src/utils/ga.ts index 24334a8..c85b517 100644 --- a/src/utils/ga.ts +++ b/src/utils/ga.ts @@ -3,13 +3,6 @@ import { AutocompleteConfig, defaultGaConfig } from "../config" const localStorageKey = "nostoAutocomplete:gaEvent" -type GaNamespace = { - (): void - getAll(): { - send(type: string, url: string): void - }[] -} - export function trackGaPageView(options?: { delay?: boolean title?: string @@ -21,20 +14,14 @@ export function trackGaPageView(options?: { location = window.location.href, } = options || {} - const windowObj = window as { - ga?: GaNamespace - gtag?: unknown - google_tag_manager?: unknown - } - if (delay) { saveToLocalStorage(title, location) } else { - if ("gtag" in windowObj && typeof windowObj.gtag === "function") { + if ("gtag" in window && typeof window.gtag === "function") { const accounts = - "google_tag_manager" in windowObj && - typeof windowObj.google_tag_manager === "object" - ? Object.keys(windowObj.google_tag_manager || []).filter( + "google_tag_manager" in window && + typeof window.google_tag_manager === "object" + ? Object.keys(window.google_tag_manager || []).filter( e => { return e.substring(0, 2) == "G-" } @@ -43,28 +30,28 @@ export function trackGaPageView(options?: { if (accounts.length > 1) { for (let i = 0; i < accounts.length; i++) { - windowObj.gtag("event", "page_view", { + window.gtag("event", "page_view", { page_title: title, page_location: location, send_to: accounts[i], }) } } else { - windowObj.gtag("event", "page_view", { + window.gtag("event", "page_view", { page_title: title, page_location: location, }) } } if ( - "ga" in windowObj && - typeof windowObj.ga === "function" && - "getAll" in windowObj.ga && - typeof windowObj.ga.getAll === "function" + "ga" in window && + typeof window.ga === "function" && + "getAll" in window.ga && + typeof window.ga.getAll === "function" ) { try { const url = new URL(location) - const trackers = windowObj.ga!.getAll() + const trackers = window.ga!.getAll() if (trackers?.length > 0) { trackers[0]?.send("pageview", url.pathname + url.search) } @@ -115,15 +102,17 @@ function saveToLocalStorage(title: string, location: string): void { localStorage.setItem(localStorageKey, JSON.stringify({ title, location })) } +interface Event { + title: string + location: string +} + function consumeLocalStorageEvent(): void { const eventString = localStorage.getItem(localStorageKey) if (typeof eventString === "string") { localStorage.removeItem(localStorageKey) try { - const event = JSON.parse(eventString) as { - title: string - location: string - } + const event: Event = JSON.parse(eventString) trackGaPageView(event) } catch (e) { log("Could not consume pageView", e, "warn") diff --git a/src/utils/history.ts b/src/utils/history.ts index fb8fbe2..7bec6f8 100644 --- a/src/utils/history.ts +++ b/src/utils/history.ts @@ -14,7 +14,7 @@ export function createHistory(size: number) { ) } catch (err) { log("Could not get history items.", err, "error") - return [] as Items + return [] } } diff --git a/src/utils/limiter.ts b/src/utils/limiter.ts index c96f699..4e5690c 100644 --- a/src/utils/limiter.ts +++ b/src/utils/limiter.ts @@ -2,7 +2,7 @@ type Callback = () => PromiseLike interface Event { getPromise?: Callback - resolve: (result?: T) => void + resolve: (result: T | PromiseLike) => void reject: (...args: unknown[]) => void number: number } @@ -22,12 +22,12 @@ export function createLimiter( function limited(getPromise?: Callback): PromiseLike { return new Promise((resolve, reject) => { currentNumber += 1 - const event = { + const event: Event = { getPromise, resolve, reject, number: currentNumber, - } as Event + } removeOldEvents() if (events.length < noLimitCount) { execute(event) @@ -82,6 +82,7 @@ export function createLimiter( event.reject(e) } } else { + // @ts-expect-error no result given event.resolve() } } diff --git a/src/utils/state.ts b/src/utils/state.ts index ea0f2f1..8fc93ed 100644 --- a/src/utils/state.ts +++ b/src/utils/state.ts @@ -41,11 +41,12 @@ export const getStateActions = ({ }): StateActions => { let cancellable: Cancellable | undefined - const fetchState = (value: string, config: AutocompleteConfig) => { + const fetchState = (value: string, config: AutocompleteConfig): PromiseLike => { if (typeof config.fetch === "function") { return config.fetch(value) } else { - return search( + // @ts-expect-error type mismatch + return search( { query: value, ...config.fetch, @@ -57,14 +58,15 @@ export const getStateActions = ({ ) } } - - function getHistoryState(query: string) { + + function getHistoryState(query: string): PromiseLike { + // @ts-expect-error type mismatch return Promise.resolve({ query: { query, }, history: history?.getItems() - } as State) + }) } return { @@ -79,7 +81,8 @@ export const getStateActions = ({ } return ( - cancellable?.promise ?? Promise.resolve({} as State) + // @ts-expect-error type mismatch + cancellable?.promise ?? Promise.resolve({}) ) }, addHistoryItem: (item: string) => { From 862339bc389c91626b205afac534c9641d6a75ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20Westk=C3=A4mper?= Date: Tue, 5 Mar 2024 09:46:16 +0200 Subject: [PATCH 2/2] Scope async usage --- src/api/client.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/api/client.ts b/src/api/client.ts index 9eca23a..8e48968 100644 --- a/src/api/client.ts +++ b/src/api/client.ts @@ -96,14 +96,14 @@ export function getNostoClient(): PromiseLike { * @param error Error instance to log * @param level Log level string */ -export async function log(message: string, error: unknown, level: LogLevel): Promise +export function log(message: string, error: unknown, level: LogLevel): void /** * * @param error Error instance to log * @param level Log level string */ -export async function log(error: unknown, level: LogLevel): Promise -export async function log( +export function log(error: unknown, level: LogLevel): void +export function log( msgOrError: unknown, errorOrLevel: unknown, optLevel?: LogLevel @@ -113,8 +113,10 @@ export async function log( // @ts-expect-error type mismatch const level: LogLevel = (optLevel || (typeof errorOrLevel === "string" ? errorOrLevel : "error")) if (error) { - const api = await getNostoClient() - api.captureError(error, "nostoAutocomplete", level) + (async () => { + const api = await getNostoClient() + api.captureError(error, "nostoAutocomplete", level) + })() } console[level](...[msg, error].filter(Boolean)) }