diff --git a/package-lock.json b/package-lock.json index ef12357..baf0614 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,12 +18,12 @@ "webextension-polyfill": "^0.10.0" }, "devDependencies": { - "@types/chrome": "^0.0.140", + "@types/chrome": "^0.0.246", "@types/jest": "^25.1.4", "@types/jquery": "^3.3.33", "@types/react": "^17.0.0", "@types/react-dom": "^17.0.0", - "@types/webextension-polyfill": "^0.9.1", + "@types/webextension-polyfill": "^0.10.4", "copy-webpack-plugin": "^6.1.0", "css-loader": "^6.8.1", "glob": "^7.1.6", @@ -35,7 +35,7 @@ "ts-loader": "^6.2.1", "typescript": "5.2.2", "web-ext": "^7.4.0", - "webpack": "5.74.0", + "webpack": "5.76.0", "webpack-cli": "4.9.1", "webpack-merge": "5.9.0" } @@ -1710,9 +1710,9 @@ } }, "node_modules/@types/chrome": { - "version": "0.0.140", - "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.140.tgz", - "integrity": "sha512-qv4rcmQMBCFDNuDgQgzThyMli8PyKmP6co6ySZbIsLfdi0AZ78k1+jUXBv+VjYMDfsO5GKvm8WNruLInxOrEQA==", + "version": "0.0.246", + "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.246.tgz", + "integrity": "sha512-MxGxEomGxsJiL9xe/7ZwVgwdn8XVKWbPvxpVQl3nWOjrS0Ce63JsfzxUc4aU3GvRcUPYsfufHmJ17BFyKxeA4g==", "dev": true, "dependencies": { "@types/filesystem": "*", @@ -1887,9 +1887,9 @@ "dev": true }, "node_modules/@types/webextension-polyfill": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/@types/webextension-polyfill/-/webextension-polyfill-0.9.2.tgz", - "integrity": "sha512-op8YeoK60K6/GIx3snWs3JowBZ+/aeSnZzZuuwynA7VARWfzr3st9aQNk9RWfoBx5xqlNZjAsh2QttPUZnabCw==", + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/@types/webextension-polyfill/-/webextension-polyfill-0.10.4.tgz", + "integrity": "sha512-pvEIqAZEbJRzaqTaWq3xlUoMWa3+euZHHz+VZHCzHWW+jOf8qLOq9wXy38U+WiPG3108SJC/wNc1X6vPC5TcjQ==", "dev": true }, "node_modules/@types/yargs": { @@ -10860,9 +10860,9 @@ "peer": true }, "node_modules/webpack": { - "version": "5.74.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.74.0.tgz", - "integrity": "sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==", + "version": "5.76.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.0.tgz", + "integrity": "sha512-l5sOdYBDunyf72HW8dF23rFtWq/7Zgvt/9ftMof71E/yUb1YLOBmTgA2K4vQthB3kotMrSj609txVE0dnr2fjA==", "dev": true, "dependencies": { "@types/eslint-scope": "^3.7.3", diff --git a/package.json b/package.json index 74c70ba..1938df0 100644 --- a/package.json +++ b/package.json @@ -26,12 +26,12 @@ "webextension-polyfill": "^0.10.0" }, "devDependencies": { - "@types/chrome": "^0.0.140", + "@types/chrome": "^0.0.246", "@types/jest": "^25.1.4", "@types/jquery": "^3.3.33", "@types/react": "^17.0.0", "@types/react-dom": "^17.0.0", - "@types/webextension-polyfill": "^0.9.1", + "@types/webextension-polyfill": "^0.10.4", "copy-webpack-plugin": "^6.1.0", "css-loader": "^6.8.1", "glob": "^7.1.6", @@ -43,7 +43,7 @@ "ts-loader": "^6.2.1", "typescript": "5.2.2", "web-ext": "^7.4.0", - "webpack": "5.74.0", + "webpack": "5.76.0", "webpack-cli": "4.9.1", "webpack-merge": "5.9.0" } diff --git a/public/manifest.json b/public/manifest.json index 3a35012..286df08 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -5,7 +5,7 @@ "version": "3.0", "options_ui": { "page": "options.html", - "browser_style": true, + "browser_style": false, "open_in_tab": true }, "browser_specific_settings": { diff --git a/src/api/runtime.ts b/src/api/runtime.ts index cf15651..45b973d 100644 --- a/src/api/runtime.ts +++ b/src/api/runtime.ts @@ -35,7 +35,6 @@ function getBackgroundPage(callback: (backgroundPage: any) => void): void { * Open the options page of the extension, if defined in the manifest. */ function openOptionsPage(): void { - console.log('here') if (typeof browser !== 'undefined' && browser.runtime) { // Firefox WebExtensions API browser.runtime.openOptionsPage(); diff --git a/src/index.ts b/src/index.ts index 162db1c..f1a2680 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,8 @@ import TOTP from './lib/TOTP'; import { copyToClipboard } from './utils/copy-to-clipboard'; import { fillInput } from './utils/fill-input'; import { api } from './api'; +import { StorageItems } from './interfaces/storage-items.interface'; +import { RuntimeMessage, RuntimeMessageId } from './interfaces/runtime-message.interface'; // Uncomment this block to reset the secret for debugging purposes /*api.storage.setStorageItems({ @@ -9,8 +11,8 @@ import { api } from './api'; secret: null, });*/ -api.storage.getStorageItems(['popUp'], (key: { popUp: boolean }) => { - if (key.popUp) { +api.storage.getStorageItems(['popUp'], ({ popUp }: StorageItems) => { + if (popUp) { api.browserAction.setBrowserActionPopup('../popup.html'); } else { api.browserAction.setBrowserActionPopup(''); @@ -18,61 +20,42 @@ api.storage.getStorageItems(['popUp'], (key: { popUp: boolean }) => { }); api.browserAction.onClicked(() => { - let clipboard: boolean; - let password: string; let totp: TOTP; api.storage.getStorageItems( ['secret', 'pass', 'clipboard'], - (key: { secret: string; pass: string; clipboard: boolean }) => { - if (!key.secret) { + ({ secret, pass, clipboard }: StorageItems) => { + if (!secret) { api.runtime.openOptionsPage(); + return; } - clipboard = key.clipboard; - - password = key.pass ? key.pass : ''; - if (!totp) { - totp = new TOTP(key.secret); + totp = new TOTP(secret); } + const passWithOtp = (pass ?? '') + totp?.getOTP(); + if (clipboard) { - return copyToClipboard(password + totp?.getOTP()); + copyToClipboard(passWithOtp); + return; } - fillInput(password, totp?.getOTP()); + fillInput(passWithOtp); } ); }); -api.runtime.onMessage(function (request, sender) { - if (request.setting == 'popup') { - api.browserAction.setBrowserActionPopup('../popup.html'); - } +api.runtime.onMessage(function(request: RuntimeMessage, sender) { + if (request.id === RuntimeMessageId.ModeSetting) { + if (request.payload.setting == 'popup') { + api.browserAction.setBrowserActionPopup('../popup.html'); + } - if (request.setting == 'click') { - api.browserAction.setBrowserActionPopup(''); + if (request.payload.setting == 'click') { + api.browserAction.setBrowserActionPopup(''); + } } -}); - -api.runtime.onMessage((msg, sender) => { - return new Promise((resolve) => { - if (msg.user) { - const input: HTMLInputElement = document.querySelectorAll("input[type='password']")[0] as HTMLInputElement; - - if (!input) { - alert('No he encontrado donde poner la contraseña ☹️'); - resolve('No input element found'); - } else { - input.value = msg.user.password + msg.user.otp; - const form = input.closest('form'); - form.submit(); - resolve(input?.value); - } - } else { - resolve(null); - } - }); }); + diff --git a/src/interfaces/runtime-message.interface.ts b/src/interfaces/runtime-message.interface.ts new file mode 100644 index 0000000..24a00c7 --- /dev/null +++ b/src/interfaces/runtime-message.interface.ts @@ -0,0 +1,12 @@ +export interface MessageModeSetting { + setting: 'popup' | 'click'; +} + +export enum RuntimeMessageId { + ModeSetting = 'MODE_SETTING', +} + +export interface RuntimeMessage { + id: RuntimeMessageId, + payload: MessageModeSetting +} diff --git a/src/interfaces/storage-items.interface.ts b/src/interfaces/storage-items.interface.ts new file mode 100644 index 0000000..e9bfde3 --- /dev/null +++ b/src/interfaces/storage-items.interface.ts @@ -0,0 +1,7 @@ +export interface StorageItems { + pass: string | undefined; + secret: string; + clipboard: boolean; + popUp: boolean; + fillInput: boolean; +} diff --git a/src/options.tsx b/src/options.tsx index 16596c8..e7f55a0 100644 --- a/src/options.tsx +++ b/src/options.tsx @@ -4,16 +4,18 @@ import { QrReader } from 'react-qr-reader'; import { api } from './api'; import { useTranslation } from 'react-i18next'; import './i18n/i18n'; +import { StorageItems } from './interfaces/storage-items.interface'; +import { RuntimeMessage, RuntimeMessageId } from './interfaces/runtime-message.interface'; const Options = () => { const toptRegex = 'otpauth:\\/\\/([ht]otp)\\/(?:[a-zA-Z0-9%]+:)?([^\\?]+)\\?secret=([0-9A-Za-z]+)(?:.*(?:(''); - const [secret, setSecret] = useState(''); - const [clipboard, setClipboard] = useState(false); - const [popUp, setPopUp] = useState(false); - const [fillInput, setFillInput] = useState(!popUp && !clipboard); + const [pass, setPass] = useState(''); + const [secret, setSecret] = useState(''); + const [clipboard, setClipboard] = useState(false); + const [popUp, setPopUp] = useState(false); + const [fillInput, setFillInput] = useState(!popUp && !clipboard); const [status, setStatus] = useState(''); const [displayQr, setDisplayQr] = useState(false); @@ -26,7 +28,7 @@ const Options = () => { useEffect(() => { api.storage.getStorageItems( ['secret', 'pass', 'clipboard', 'popUp'], - (key: { secret: string; pass: string; clipboard: boolean; popUp: boolean }) => { + (key: StorageItems) => { if (!secret) { setPass(key.pass ? key.pass : ''); setSecret(key.secret ? key.secret : ''); @@ -45,14 +47,15 @@ const Options = () => { }, [clipboard, popUp]); const saveOptions = () => { - api.storage.setStorageItems({ pass, secret, clipboard, popUp }, () => { + const options: StorageItems = { pass, secret, clipboard, popUp, fillInput }; + api.storage.setStorageItems(options, () => { let timeoutId: NodeJS.Timeout; - let message: { setting: string }; + let message: RuntimeMessage; if (popUp) { - message = { setting: 'popup' }; + message = { id: RuntimeMessageId.ModeSetting, payload: { setting: 'popup' } }; } else { - message = { setting: 'click' }; + message = { id: RuntimeMessageId.ModeSetting, payload: { setting: 'click' } }; } api.runtime.sendMessage(message, () => { diff --git a/src/popup.tsx b/src/popup.tsx index 87ed2a1..8bac148 100644 --- a/src/popup.tsx +++ b/src/popup.tsx @@ -7,6 +7,7 @@ import { api } from './api'; import './i18n/i18n'; import './popup.css'; import { useTranslation } from 'react-i18next'; +import { StorageItems } from './interfaces/storage-items.interface'; const Popup = () => { const [theme, setTheme] = useState<'light' | 'dark'>('dark'); @@ -41,12 +42,13 @@ const Popup = () => { const fillOtp = () => { setOtp(totp.otp); - - fillInput(password, totp?.getOTP()); + const passWithOtp = (password ?? '') + totp?.getOTP(); + fillInput(passWithOtp); }; const copyOtpToClipboard = () => { - copyToClipboard(password + totp.otp); + const passWithOtp = (password ?? '') + totp?.getOTP(); + copyToClipboard(passWithOtp); }; const goToOptions = () => { @@ -55,7 +57,7 @@ const Popup = () => { api.storage.getStorageItems( ['secret', 'pass', 'clipboard'], - (key: { secret: string; pass: string; clipboard: boolean }) => { + (key: StorageItems) => { if (!key.secret) { return goToOptions(); } diff --git a/src/utils/copy-to-clipboard.ts b/src/utils/copy-to-clipboard.ts index 14a0a18..64daa95 100644 --- a/src/utils/copy-to-clipboard.ts +++ b/src/utils/copy-to-clipboard.ts @@ -1,3 +1,11 @@ export const copyToClipboard = (str: string) => { - navigator.clipboard.writeText(str); + const el = document.createElement('input'); + el.value = str; + el.setAttribute('readonly', ''); + el.style.display = 'absolute'; + el.style.left = '-9999px'; + document.body.appendChild(el); + el.select(); + document.execCommand('copy'); + document.body.removeChild(el); }; diff --git a/src/utils/fill-input.ts b/src/utils/fill-input.ts index ebb6560..91a6a48 100644 --- a/src/utils/fill-input.ts +++ b/src/utils/fill-input.ts @@ -1,12 +1,13 @@ import { api } from '../api'; -export const fillInput = async (password: string, otp: number) => { +export const fillInput = async (text: string) => { + console.log(text); await api.tabs.executeScript({ code: ` if (!document.querySelectorAll("input[type='password']")[0]) { alert('No he encontrado donde poner la contraseña ☹️'); } else { - document.querySelectorAll("input[type='password']")[0].value = '${password + otp}'; + document.querySelectorAll("input[type='password']")[0].value = '${text}'; document.querySelectorAll("input[type='password']")[0].closest('form').submit(); }`, });