Skip to content

Commit

Permalink
feat(): cross-browser compatibility (Firefox and Chrome support)
Browse files Browse the repository at this point in the history
  • Loading branch information
gloaysa committed Sep 26, 2023
1 parent dea4e55 commit 1958f62
Show file tree
Hide file tree
Showing 12 changed files with 435 additions and 77 deletions.
20 changes: 6 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,45 +2,37 @@

* It adds an OTP authentication number to the first input of type password in the document and sends the form, so the user can authenticate with it.
* As an option, the user can add a password to the configuration, so the input will be filled with a combination of password + OTP.
* In the configuration the user can select to store the OTP in the clipboard.
* In the configuration the user can select to store the OTP in the clipboard or open a popup with different choices.
* The QR code can be read from the plugin's configuration page and it will fill the secret automatically.

## Live version
- [Firefox](https://addons.mozilla.org/en-US/firefox/addon/no-otp/)
- [Chrome](https://chrome.google.com/webstore/detail/no%20otp/epfcbfnadaigbokbekcipecemoaiaeni)

This is a Chrome Extension app written in React.
This is a Web Extension app written in React.

- It adds an OTP authentication number to the first input of type password in the document and sends the form, so the user can authenticate with it.
- As an option, the user can add a password to the configuration, so the input will be filled with a combination of password + OTP.
- In the configuration the user can select to store the OTP in the clipboard.
- The QR code can be read from the plugin's configuration page and it will fill the secret automatically.

## Project Structure

* src/typescript: TypeScript source files
* src/assets: static files
* plugin: Browser Extension directory

## Install the Plugin

You can install this plugin [here](https://chrome.google.com/webstore/detail/no%20otp/epfcbfnadaigbokbekcipecemoaiaeni)

If you prefer to install a local version of it from this repository:

- Download the repo with `git clone git@github.com:gloaysa/no-otp.git`
- Install the dependencies: `npm install`
- Build it with `npm run build`
- Then go to chrome://extensions/ and activate the developer mode (if not active) at the right top corner.
- Click on Load Unpacked and load the `dist` folder.
- Then install it manually on your browser.

## Build in watch mode
## Development
### Build in watch mode

```
npm run watch
```

## Load extension on your browser
### Load extension on your browser

Load `plugin` directory

Expand Down
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": "1.0.4",
"version": "3",
"description": "Add to the input your OTP",
"main": "index.js",
"scripts": {
Expand Down
5 changes: 3 additions & 2 deletions public/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@
"manifest_version": 2,
"name": "NO+OTP",
"description": "Add to the input your password + OTP",
"version": "1.0.4",
"version": "3.0",
"options_ui": {
"page": "options.html",
"browser_style": true
"browser_style": true,
"open_in_tab": true
},
"browser_specific_settings": {
"gecko": {
Expand Down
83 changes: 83 additions & 0 deletions src/api/browser-action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/**
* Wrapper function to set the popup of the browser action.
* @param popup - The URL of the HTML file to be used as a popup. An empty string clears the popup.
* @returns A Promise that resolves when the popup is set.
*/
function setBrowserActionPopup(popup: string): Promise<void> {
return new Promise<void>((resolve, reject) => {
if (typeof browser !== 'undefined' && browser.browserAction) {
// Firefox WebExtensions API
browser.browserAction.setPopup({ popup })
.then(() => {
resolve();
})
.catch((error) => {
reject(error);
});
} else if (typeof chrome !== 'undefined' && chrome.browserAction) {
// Chrome Extension API
chrome.browserAction.setPopup({ popup }, () => {
if (chrome.runtime.lastError) {
reject(chrome.runtime.lastError);
} else {
resolve();
}
});
} else {
reject(new Error('Unsupported browser.'));
}
});
}

/**
* Wrapper function to set the title of the browser action.
* @param title - The title text to set.
* @returns A Promise that resolves when the title is set.
*/
function setBrowserActionTitle(title: string): Promise<void> {
return new Promise<void>((resolve, reject) => {
if (typeof browser !== 'undefined' && browser.browserAction) {
// Firefox WebExtensions API
browser.browserAction.setTitle({ title })
.then(() => {
resolve();
})
.catch((error) => {
reject(error);
});
} else if (typeof chrome !== 'undefined' && chrome.browserAction) {
// Chrome Extension API
chrome.browserAction.setTitle({ title }, () => {
if (chrome.runtime.lastError) {
reject(chrome.runtime.lastError);
} else {
resolve();
}
});
} else {
reject(new Error('Unsupported browser.'));
}
});
}

/**
* Wrapper function to add a click event listener for the browser action.
* @param listener - The callback function to be executed when the browser action is clicked.
*/
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) {
// Chrome Extension API
chrome.browserAction.onClicked.addListener(listener);
} else {
console.error('Unsupported browser.');
}
}

export const browserAction = {
setBrowserActionPopup,
setBrowserActionTitle,
onClicked
}
11 changes: 11 additions & 0 deletions src/api/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { storage } from './storage';
import { runtime } from './runtime';
import { browserAction } from './browser-action';
import { tabs } from './tabs';

export const api = {
storage,
runtime,
browserAction,
tabs,
}
89 changes: 89 additions & 0 deletions src/api/runtime.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/**
* Get the extension ID, which is unique to each extension.
* @returns The extension ID.
*/
function getExtensionId(): string | null {
if (typeof browser !== 'undefined' && browser.runtime) {
// Firefox WebExtensions API
return browser.runtime.id;
} else if (typeof chrome !== 'undefined' && chrome.runtime) {
// Chrome Extension API
return chrome.runtime.id;
} else {
console.error('Unsupported browser.');
return null;
}
}

/**
* Get the background page of the extension.
* @param callback - A callback function that receives the background page as an argument.
*/
function getBackgroundPage(callback: (backgroundPage: any) => void): void {
if (typeof browser !== 'undefined' && browser.runtime) {
// Firefox WebExtensions API
browser.runtime.getBackgroundPage().then(callback);
} else if (typeof chrome !== 'undefined' && chrome.runtime) {
// Chrome Extension API
chrome.runtime.getBackgroundPage(callback);
} else {
console.error('Unsupported browser.');
}
}

/**
* 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();
} else if (typeof chrome !== 'undefined' && chrome.runtime) {
// Chrome Extension API
chrome.runtime.openOptionsPage();
} else {
console.error('Unsupported browser.');
}
}

/**
* Send a message to the extension's background script.
* @param message - The message to send.
* @param callback - A callback function that will be called with the response (if any).
*/
function sendMessage(message: any, callback?: (response: any) => void): void {
if (typeof browser !== 'undefined' && browser.runtime) {
// Firefox WebExtensions API
browser.runtime.sendMessage(message, callback);
} else if (typeof chrome !== 'undefined' && chrome.runtime) {
// Chrome Extension API
chrome.runtime.sendMessage(message, callback);
} else {
console.error('Unsupported browser.');
}
}

/**
* Wrapper function to add a message event listener for the runtime.
* @param listener - The callback function to be executed when a message is received.
*/
function onMessage(listener: (msg: any, sender: any) => void): void {
if (typeof browser !== 'undefined' && browser.runtime) {
// Firefox WebExtensions API
browser.runtime.onMessage.addListener(listener);
} else if (typeof chrome !== 'undefined' && chrome.runtime) {
// Chrome Extension API
chrome.runtime.onMessage.addListener(listener);
} else {
console.error('Unsupported browser.');
}
}

export const runtime = {
getExtensionId,
getBackgroundPage,
openOptionsPage,
sendMessage,
onMessage,
};
76 changes: 76 additions & 0 deletions src/api/storage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/**
* Wrapper function to get items from storage.
* @param keys - A string, array of strings, or object with keys to retrieve from storage.
* @param callback - A callback function that receives the retrieved items.
*/
function getStorageItems(
keys: string | string[] | Record<string, any>,
callback: (result: Record<string, any>) => void
): void {
if (typeof browser !== 'undefined' && browser.storage) {
// Firefox WebExtensions API
browser.storage.sync.get(keys).then(callback);
} else if (typeof chrome !== 'undefined' && chrome.storage) {
// Chrome Extension API
chrome.storage.sync.get(keys, callback);
} else {
console.error('Unsupported browser.');
}
}

/**
* Wrapper function to set items in storage.
* @param items - An object containing items to be stored.
* @param callback - A callback function to be executed after the items have been stored.
*/
function setStorageItems(items: Record<string, any>, callback?: () => void): void {
if (typeof browser !== 'undefined' && browser.storage) {
// Firefox WebExtensions API
browser.storage.sync.set(items).then(callback);
} else if (typeof chrome !== 'undefined' && chrome.storage) {
// Chrome Extension API
chrome.storage.sync.set(items, callback);
} else {
console.error('Unsupported browser.');
}
}

/**
* Wrapper function to remove items from storage.
* @param keys - A string or array of strings representing keys to remove from storage.
* @param callback - A callback function to be executed after the items have been removed.
*/
function removeStorageItems(keys: string | string[], callback?: () => void): void {
if (typeof browser !== 'undefined' && browser.storage) {
// Firefox WebExtensions API
browser.storage.sync.remove(keys).then(callback);
} else if (typeof chrome !== 'undefined' && chrome.storage) {
// Chrome Extension API
chrome.storage.sync.remove(keys, callback);
} else {
console.error('Unsupported browser.');
}
}

/**
* Wrapper function to clear all items from storage.
* @param callback - A callback function to be executed after storage is cleared.
*/
function clearStorage(callback?: () => void): void {
if (typeof browser !== 'undefined' && browser.storage) {
// Firefox WebExtensions API
browser.storage.sync.clear().then(callback);
} else if (typeof chrome !== 'undefined' && chrome.storage) {
// Chrome Extension API
chrome.storage.sync.clear(callback);
} else {
console.error('Unsupported browser.');
}
}

export const storage = {
getStorageItems,
setStorageItems,
removeStorageItems,
clearStorage
}
Loading

0 comments on commit 1958f62

Please sign in to comment.