diff --git a/src/execute.ts b/src/execute.ts index a25dc2b..8401cb4 100644 --- a/src/execute.ts +++ b/src/execute.ts @@ -1,7 +1,13 @@ +function hasResult( + injectionResult: chrome.scripting.InjectionResult, +): injectionResult is chrome.scripting.InjectionResult & { result: T } { + return Object.hasOwn(injectionResult, 'result'); +} + export default async function execute( func: (argument: T) => U, argument: T, -): Promise { +): Promise | null> { let tab; try { @@ -12,14 +18,14 @@ export default async function execute( const tabId = tab.id; - if (tabId === undefined) { + if (typeof tabId !== 'number') { return null; } - let result; + let injectionResult: chrome.scripting.InjectionResult>; try { - [{ result }] = await chrome.scripting.executeScript({ + [injectionResult] = await chrome.scripting.executeScript<[T], U>({ target: { tabId }, func, args: [argument], @@ -28,5 +34,9 @@ export default async function execute( return null; } - return result as U | null; + if (!hasResult(injectionResult)) { + return null; + } + + return injectionResult.result; } diff --git a/src/fill-in-password.ts b/src/fill-in-password.ts index 68e0bdd..9159a14 100644 --- a/src/fill-in-password.ts +++ b/src/fill-in-password.ts @@ -9,12 +9,12 @@ export default async function fillInPassword(generatedPassword: string): Promise // If the active element is an iframe, descend into it to find the innermost active element. while (element instanceof iframeElementType) { const contentDocument = element.contentDocument; - const contentWindow = element.contentWindow as (Window & typeof globalThis) | null; + const contentWindow = element.contentWindow; if (contentDocument !== null && contentWindow !== null) { element = contentDocument.activeElement; - iframeElementType = contentWindow.HTMLIFrameElement; - inputElementType = contentWindow.HTMLInputElement; + iframeElementType = contentWindow.window.HTMLIFrameElement; + inputElementType = contentWindow.window.HTMLInputElement; } } diff --git a/src/get-is-password-field-active.ts b/src/get-is-password-field-active.ts index 5a21115..324e742 100644 --- a/src/get-is-password-field-active.ts +++ b/src/get-is-password-field-active.ts @@ -9,12 +9,12 @@ export default async function getIsPasswordFieldActive(): Promise void> = {}; +const requests = new Map void>(); // This is the handler for incoming responses. -worker.onmessage = (event: MessageEvent) => { - requests[event.data.messageId](event.data.generatedPassword); - delete requests[event.data.messageId]; -}; +worker.addEventListener('message', (event: MessageEvent): void => { + const resolve = requests.get(event.data.messageId); -export default function hashpass(domain: string, universalPassword: string): Promise { + if (resolve === undefined) { + return; + } + + resolve(event.data.generatedPassword); + requests.delete(event.data.messageId); +}); + +export default async function hashpass(domain: string, universalPassword: string): Promise { return new Promise((resolve) => { const request: Request = { messageId: nextMessageId, @@ -23,8 +29,9 @@ export default function hashpass(domain: string, universalPassword: string): Pro universalPassword, }; - requests[nextMessageId] = resolve; + requests.set(nextMessageId, resolve); + // oxlint-disable-next-line unicorn/require-post-message-target-origin -- Worker.postMessage does not take targetOrigin. worker.postMessage(request); nextMessageId = (nextMessageId + 1) % Number.MAX_SAFE_INTEGER; diff --git a/src/worker.ts b/src/worker.ts index 6c39d12..d33a584 100644 --- a/src/worker.ts +++ b/src/worker.ts @@ -1,7 +1,7 @@ import hashpass from './hashpass.ts'; import type { Request, Response } from './worker-protocol.ts'; -self.onmessage = (event: MessageEvent) => { +self.addEventListener('message', (event: MessageEvent): void => { const request = event.data; const response: Response = { @@ -9,5 +9,6 @@ self.onmessage = (event: MessageEvent) => { generatedPassword: hashpass(request.domain, request.universalPassword), }; + // oxlint-disable-next-line unicorn/require-post-message-target-origin -- WorkerGlobalScope.postMessage does not take targetOrigin. self.postMessage(response); -}; +}); diff --git a/tsconfig.app.json b/tsconfig.app.json deleted file mode 100644 index 05453b3..0000000 --- a/tsconfig.app.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "compilerOptions": { - "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", - "target": "es2023", - "lib": ["ES2023", "DOM"], - "module": "esnext", - "types": ["vite/client", "chrome"], - "allowArbitraryExtensions": true, - "skipLibCheck": true, - - /* Bundler mode */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "verbatimModuleSyntax": true, - "moduleDetection": "force", - "noEmit": true, - "jsx": "react-jsx", - - /* Linting */ - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "erasableSyntaxOnly": true, - "noFallthroughCasesInSwitch": true - }, - "include": ["src"] -} diff --git a/tsconfig.json b/tsconfig.json index d32ff68..0528293 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,4 +1,21 @@ { - "files": [], - "references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }] + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo", + "target": "es2023", + "lib": ["ES2023", "DOM"], + "module": "esnext", + "moduleResolution": "bundler", + "types": ["chrome", "vite/client"], + "allowArbitraryExtensions": true, + "allowImportingTsExtensions": true, + "erasableSyntaxOnly": true, + "jsx": "react-jsx", + "moduleDetection": "force", + "noEmit": true, + "noFallthroughCasesInSwitch": true, + "skipLibCheck": true, + "strict": true, + "verbatimModuleSyntax": true + }, + "include": ["src", "vite.config.ts"] } diff --git a/tsconfig.node.json b/tsconfig.node.json deleted file mode 100644 index 85f3474..0000000 --- a/tsconfig.node.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "compilerOptions": { - "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", - "target": "es2023", - "lib": ["ES2023"], - "types": ["node"], - "skipLibCheck": true, - - /* Bundler mode */ - "module": "nodenext", - "allowImportingTsExtensions": true, - "verbatimModuleSyntax": true, - "moduleDetection": "force", - "noEmit": true, - - /* Linting */ - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "erasableSyntaxOnly": true, - "noFallthroughCasesInSwitch": true - }, - "include": ["vite.config.ts"] -} diff --git a/vite.config.ts b/vite.config.ts index 3ee6fad..6b3600a 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,27 +1,20 @@ import { defineConfig, lazyPlugins } from 'vite-plus'; import react from '@vitejs/plugin-react'; -// https://vite.dev/config/ export default defineConfig({ // Use relative asset URLs so the build works both as an extension popup and as a static website // served from any path. base: './', - fmt: { singleQuote: true, ignorePatterns: ['**/*.md'] }, + fmt: { + ignorePatterns: ['**/*.md'], + singleQuote: true, + }, lint: { - plugins: ['react', 'typescript', 'oxc'], - rules: { - 'react/rules-of-hooks': 'error', - 'react/only-export-components': [ - 'warn', - { - allowConstantExport: true, - }, - ], - 'vite-plus/prefer-vite-plus-imports': 'error', - }, - options: { - typeAware: true, - typeCheck: true, + categories: { + correctness: 'deny', + perf: 'deny', + restriction: 'deny', + suspicious: 'deny', }, jsPlugins: [ { @@ -29,6 +22,18 @@ export default defineConfig({ specifier: 'vite-plus/oxlint-plugin', }, ], + options: { + typeAware: true, + typeCheck: true, + }, + plugins: ['oxc', 'react', 'typescript', 'unicorn'], + rules: { + 'no-undefined': 'allow', + 'oxc/no-async-await': 'allow', + 'react/jsx-filename-extension': 'allow', + 'react/jsx-no-literals': 'allow', + 'react/react-in-jsx-scope': 'allow', + }, }, plugins: lazyPlugins(() => [react()]), });