Skip to content

Commit

Permalink
feat: migrate to manifest v3
Browse files Browse the repository at this point in the history
  • Loading branch information
gloaysa committed Nov 15, 2024
1 parent 2cc4fbb commit 448928b
Show file tree
Hide file tree
Showing 11 changed files with 158 additions and 53 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "no-otp",
"version": "3",
"version": "3.1",
"description": "Add to the input your OTP",
"main": "index.js",
"scripts": {
Expand Down
12 changes: 0 additions & 12 deletions public/index.html

This file was deleted.

10 changes: 6 additions & 4 deletions public/manifest.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
{
"manifest_version": 2,
"manifest_version": 3,
"name": "NO+OTP",
"description": "Add to the input your password + OTP",
"version": "3.0",
"version": "3.1",
"action": {},
"options_ui": {
"page": "options.html",
"browser_style": false,
Expand All @@ -22,7 +23,8 @@
"default_title": "NO+OTP"
},
"background": {
"page": "index.html"
"service_worker": "js/index.js",
"type": "module"
},
"permissions": ["storage", "activeTab", "clipboardWrite"]
"permissions": ["storage", "activeTab", "clipboardWrite", "offscreen", "scripting"]
}
3 changes: 3 additions & 0 deletions public/offscreen.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<!doctype html>
<textarea id="text"></textarea>
<script src="offscreen.js"></script>
74 changes: 74 additions & 0 deletions public/offscreen.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Once the message has been posted from the service worker, checks are made to
// confirm the message type and target before proceeding. This is so that the
// module can easily be adapted into existing workflows where secondary uses for
// the document (or alternate offscreen documents) might be implemented.

// Registering this listener when the script is first executed ensures that the
// offscreen document will be able to receive messages when the promise returned
// by `offscreen.createDocument()` resolves.
chrome.runtime.onMessage.addListener(handleMessages);

// This function performs basic filtering and error checking on messages before
// dispatching the
// message to a more specific message handler.
async function handleMessages(message) {
// Return early if this message isn't meant for the offscreen document.
if (message.target !== 'offscreen-doc') {
return;
}

// Dispatch the message to an appropriate handler.
switch (message.type) {
case 'copy-data-to-clipboard':
handleClipboardWrite(message.data);
break;
default:
console.warn(`Unexpected message type received: '${message.type}'.`);
}
}

// We use a <textarea> element for two main reasons:
// 1. preserve the formatting of multiline text,
// 2. select the node's content using this element's `.select()` method.
const textEl = document.querySelector('#text');

// Use the offscreen document's `document` interface to write a new value to the
// system clipboard.
//
// At the time this demo was created (Jan 2023) the `navigator.clipboard` API
// requires that the window is focused, but offscreen documents cannot be
// focused. As such, we have to fall back to `document.execCommand()`.
async function handleClipboardWrite(data) {
try {
// Error if we received the wrong kind of data.
if (typeof data !== 'string') {
throw new TypeError(
`Value provided must be a 'string', got '${typeof data}'.`
);
}

// `document.execCommand('copy')` works against the user's selection in a web
// page. As such, we must insert the string we want to copy to the web page
// and to select that content in the page before calling `execCommand()`.
textEl.value = data;
textEl.select();
document.execCommand('copy');
} finally {
// Job's done! Close the offscreen document.
window.close();
}
}
12 changes: 6 additions & 6 deletions src/api/browser-action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ function setBrowserActionPopup(popup: string): Promise<void> {
.catch((error) => {
reject(error);
});
} else if (typeof chrome !== 'undefined' && chrome.browserAction) {
} else if (typeof chrome !== 'undefined' && chrome.action) {
// Chrome Extension API
chrome.browserAction.setPopup({ popup }, () => {
chrome.action.setPopup({ popup }, () => {
if (chrome.runtime.lastError) {
reject(chrome.runtime.lastError);
} else {
Expand Down Expand Up @@ -45,9 +45,9 @@ function setBrowserActionTitle(title: string): Promise<void> {
.catch((error) => {
reject(error);
});
} else if (typeof chrome !== 'undefined' && chrome.browserAction) {
} else if (typeof chrome !== 'undefined' && chrome.action) {
// Chrome Extension API
chrome.browserAction.setTitle({ title }, () => {
chrome.action.setTitle({ title }, () => {
if (chrome.runtime.lastError) {
reject(chrome.runtime.lastError);
} else {
Expand All @@ -68,9 +68,9 @@ function onClicked(listener: () => void): void {
if (typeof browser !== 'undefined' && browser.browserAction) {
// Firefox WebExtensions API
browser.browserAction.onClicked.addListener(listener);
} else if (typeof chrome !== 'undefined' && chrome.browserAction) {
} else if (typeof chrome !== 'undefined' && chrome.action) {
// Chrome Extension API
chrome.browserAction.onClicked.addListener(listener);
chrome.action.onClicked.addListener(listener);
} else {
console.error('Unsupported browser.');
}
Expand Down
13 changes: 10 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ import { RuntimeMessage, RuntimeMessageId } from './interfaces/runtime-message.i
pass: null,
secret: null,
});*/
self.addEventListener('install', (event) => {
console.log('Service Worker installed');
});

self.addEventListener('activate', (event) => {
console.log('Service Worker activated');
});

api.storage.getStorageItems(['popUp'], ({ popUp }: StorageItems) => {
if (popUp) {
Expand All @@ -24,7 +31,7 @@ api.browserAction.onClicked(() => {

api.storage.getStorageItems(
['secret', 'pass', 'clipboard'],
({ secret, pass, clipboard }: StorageItems) => {
async ({ secret, pass, clipboard }: StorageItems) => {
if (!secret) {
api.runtime.openOptionsPage();
return;
Expand All @@ -37,11 +44,11 @@ api.browserAction.onClicked(() => {
const passWithOtp = (pass ?? '') + totp?.getOTP();

if (clipboard) {
copyToClipboard(passWithOtp);
await copyToClipboard(passWithOtp);
return;
}

fillInput(passWithOtp);
await fillInput(passWithOtp);
}
);
});
Expand Down
10 changes: 6 additions & 4 deletions src/popup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,17 @@ const Popup = () => {
setTheme(newTheme);
}

const fillOtp = () => {
const fillOtp = async () => {
setOtp(totp.otp);
const passWithOtp = (password ?? '') + totp?.getOTP();
fillInput(passWithOtp);
await fillInput(passWithOtp);
window.close();
};

const copyOtpToClipboard = () => {
const copyOtpToClipboard = async () => {
const passWithOtp = (password ?? '') + totp?.getOTP();
copyToClipboard(passWithOtp);
await copyToClipboard(passWithOtp);
window.close();
};

const goToOptions = () => {
Expand Down
41 changes: 31 additions & 10 deletions src/utils/copy-to-clipboard.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,32 @@
export const copyToClipboard = (str: string) => {
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);
export const copyToClipboard = async (str: string) => {
if (chrome) {
// Solution 1 - As of Jan 2023, service workers cannot directly interact with
// the system clipboard using either `navigator.clipboard` or
// `document.execCommand()`. To work around this, we'll create an offscreen
// document and pass it the data we want to write to the clipboard.
await chrome.offscreen.createDocument({
url: 'offscreen.html',
reasons: [chrome.offscreen.Reason.CLIPBOARD],
justification: 'Write text to the clipboard.'
});

// Now that we have an offscreen document, we can dispatch the
// message.
await chrome.runtime.sendMessage({
type: 'copy-data-to-clipboard',
target: 'offscreen-doc',
data: str
});
}
if (document) {
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);
}
};
29 changes: 20 additions & 9 deletions src/utils/fill-input.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
import { api } from '../api';

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 = '${text}';
document.querySelectorAll("input[type='password']")[0].closest('form').submit();
}`,
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
if (tabs.length > 0 && tabs[0].id) {
chrome.scripting.executeScript({
target: { tabId: tabs[0].id },
func: (password) => {
// Injected function that fills in the password field
const passwordInput: HTMLInputElement = document.querySelector("input[type='password']");
if (!passwordInput) {
alert('No password field found ☹️');
} else {
passwordInput.value = password;
const form = passwordInput.closest('form');
if (form) {
form.submit();
}
}
},
args: [text], // Pass the password text to the injected function
});
}
});
};
5 changes: 1 addition & 4 deletions webpack/webpack.common.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,7 @@ module.exports = {
filename: '[name].js',
},
optimization: {
splitChunks: {
name: 'vendor',
chunks: 'initial',
},
splitChunks: false,
},
module: {
rules: [
Expand Down

0 comments on commit 448928b

Please sign in to comment.