feat: cross browser extension api wrapper
This commit is contained in:
parent
750f1f3bdf
commit
cf2d6e1efd
|
@ -1,2 +1 @@
|
|||
IS_PRODUCTION=false
|
||||
FOO=bar
|
||||
IS_PRODUCTION=false
|
|
@ -14,6 +14,11 @@
|
|||
"plugin:prettier/recommended"
|
||||
],
|
||||
"rules": {
|
||||
"@typescript-eslint/no-explicit-any": "warn",
|
||||
"@typescript-eslint/no-unsafe-return": "warn",
|
||||
"@typescript-eslint/no-unsafe-member-access": "warn",
|
||||
"@typescript-eslint/no-unsafe-call": "warn",
|
||||
"@typescript-eslint/no-unsafe-argument": "warn",
|
||||
"max-len": ["error", {"code": 120, "ignoreComments": true, "ignoreTemplateLiterals": true}],
|
||||
"no-console": "error",
|
||||
"semi": [ "error", "always" ],
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
node_modules
|
||||
dist
|
||||
.parcel-cache
|
||||
.parcel-cache
|
||||
|
||||
*.iml
|
|
@ -0,0 +1,4 @@
|
|||
node_modules
|
||||
.parcel-cache
|
||||
|
||||
*.iml
|
|
@ -1,6 +0,0 @@
|
|||
declare const process: {
|
||||
env: {
|
||||
ENV: string;
|
||||
URL: string;
|
||||
}
|
||||
}
|
13
README.md
13
README.md
|
@ -1,17 +1,8 @@
|
|||
# Typescript parceljs html page template
|
||||
* parceljs
|
||||
* typescript
|
||||
* linter - eslint with @typescript-eslint
|
||||
* prettier
|
||||
* pre-commit hook for lint
|
||||
* reading .env files
|
||||
* simple HMR using `window.location.reload()`
|
||||
# browser-api
|
||||
* chrome / firefox extension api wrapper
|
||||
|
||||
### Development
|
||||
```npm run dev```
|
||||
|
||||
starts server hosted on port
|
||||
```localhost:1234```
|
||||
|
||||
### Production
|
||||
``npm run prod``
|
||||
|
|
File diff suppressed because it is too large
Load Diff
26
package.json
26
package.json
|
@ -1,9 +1,19 @@
|
|||
{
|
||||
"name": "typescript-parcel-base",
|
||||
"name": "@pinmenote/browser-api",
|
||||
"version": "0.0.1",
|
||||
"author": "Michal Szczepanski",
|
||||
"license": "MIT",
|
||||
"description": "cross browser compatible api layer",
|
||||
"bugs": {
|
||||
"url": "https://github.com/pinmenote/browser-api/issues"
|
||||
},
|
||||
"homepage": "https://github.com/pinmenote/browser-api#readme",
|
||||
"source": "src/index.ts",
|
||||
"module": "dist/module.js",
|
||||
"types": "dist/types.d.ts",
|
||||
"scripts": {
|
||||
"dev": "parcel src/index.html",
|
||||
"prod": "parcel build src/index.html",
|
||||
"build": "parcel build",
|
||||
"dev": "NODE_ENV=development parcel build",
|
||||
"lint": "eslint --ext .ts src/",
|
||||
"lint:fix": "eslint --ext .ts,.tsx src/ --fix"
|
||||
},
|
||||
|
@ -11,14 +21,18 @@
|
|||
"lint"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@parcel/transformer-typescript-tsc": "^2.8.3",
|
||||
"@types/node": "^18.11.18",
|
||||
"@parcel/packager-ts": "^2.9.2",
|
||||
"@parcel/transformer-typescript-tsc": "^2.9.2",
|
||||
"@parcel/transformer-typescript-types": "^2.9.2",
|
||||
"@types/chrome": "^0.0.237",
|
||||
"@types/firefox-webext-browser": "^111.0.1",
|
||||
"@types/node": "^20.3.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.48.2",
|
||||
"@typescript-eslint/parser": "^5.48.2",
|
||||
"eslint": "^8.32.0",
|
||||
"eslint-config-prettier": "^8.6.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"parcel": "^2.8.3",
|
||||
"parcel": "^2.9.2",
|
||||
"pre-commit": "^1.2.2",
|
||||
"typescript": "^4.9.4"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
* This file is part of the pinmenote-extension distribution (https://github.com/pinmenote/pinmenote-extension).
|
||||
* Copyright (c) 2023 Michal Szczepanski.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import { Config } from './config';
|
||||
import { fnConsoleLog } from './fn.console.log';
|
||||
|
||||
export type BrowserGlobalSender = browser.runtime.MessageSender | chrome.runtime.MessageSender;
|
||||
export type BrowserGlobal = typeof chrome | typeof browser;
|
||||
export type BrowserRuntime = typeof chrome.runtime | typeof browser.runtime;
|
||||
export type BrowserTabs = typeof chrome.tabs | typeof browser.tabs;
|
||||
export type BrowserTab = chrome.tabs.Tab | browser.tabs.Tab;
|
||||
export type BrowserTabObject = chrome.tabs.Tab | browser.tabs.Tab;
|
||||
export type BrowserLocalStore = typeof chrome.storage.local | typeof browser.storage.local;
|
||||
export type BrowserDownloads = typeof chrome.downloads | typeof browser.downloads;
|
||||
export type BrowserAction = typeof chrome.action | typeof browser.browserAction;
|
||||
export type BrowserTabChangeInfo = chrome.tabs.TabChangeInfo | browser.tabs._OnUpdatedChangeInfo;
|
||||
|
||||
export interface BusMessage<T> {
|
||||
type: string;
|
||||
data?: T;
|
||||
}
|
||||
|
||||
export class BrowserApi {
|
||||
private static browserApi: BrowserGlobal;
|
||||
private static isChromeValue = false;
|
||||
|
||||
static init() {
|
||||
if (this.browserApi) return;
|
||||
try {
|
||||
this.browserApi = browser;
|
||||
} catch (e) {
|
||||
this.browserApi = chrome;
|
||||
this.isChromeValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
static get isChrome(): boolean {
|
||||
return this.isChromeValue;
|
||||
}
|
||||
|
||||
static get browser(): BrowserGlobal {
|
||||
return this.browserApi;
|
||||
}
|
||||
|
||||
static get runtime(): BrowserRuntime {
|
||||
return this.browserApi.runtime;
|
||||
}
|
||||
|
||||
static get tabs(): BrowserTabs {
|
||||
return this.browserApi.tabs;
|
||||
}
|
||||
|
||||
static activeTab = async (): Promise<BrowserTab> => {
|
||||
const tabs = await this.browserApi.tabs.query({ active: true, currentWindow: true });
|
||||
return tabs[0];
|
||||
};
|
||||
|
||||
static setActiveTabUrl = async (url: string): Promise<void> => {
|
||||
const tab = await this.activeTab();
|
||||
await this.browserApi.tabs.update(tab.id, { url });
|
||||
};
|
||||
|
||||
static get localStore(): BrowserLocalStore {
|
||||
return this.browserApi.storage.local;
|
||||
}
|
||||
|
||||
static get downloads(): BrowserDownloads {
|
||||
return this.browserApi.downloads;
|
||||
}
|
||||
|
||||
static get browserAction(): BrowserAction {
|
||||
if (this.isChromeValue) return this.browserApi.action;
|
||||
return this.browserApi.browserAction;
|
||||
}
|
||||
|
||||
static get startUrl(): string {
|
||||
return this.isChromeValue ? 'chrome-extension' : 'moz-extension';
|
||||
}
|
||||
|
||||
static get disabledUrl(): string {
|
||||
return this.isChromeValue ? 'chrome://' || 'chrome-extension://' : 'moz://' || 'moz-extension://';
|
||||
}
|
||||
|
||||
static get runtimeUrl(): string {
|
||||
if (BrowserApi.isChrome) {
|
||||
return `chrome-extension://${chrome.runtime.id}`;
|
||||
}
|
||||
return 'moz-extension://';
|
||||
}
|
||||
|
||||
static openOptionsPage(subpage = ''): void {
|
||||
if (this.isChromeValue) {
|
||||
const optionsPage = chrome.runtime.getManifest().options_ui?.page;
|
||||
if (optionsPage) window.open(`chrome-extension://${chrome.runtime.id}/${optionsPage}${subpage}`);
|
||||
return;
|
||||
}
|
||||
window.open(browser.runtime.getManifest().options_ui?.page);
|
||||
window.close();
|
||||
}
|
||||
|
||||
static shadowRoot(el: Element): ShadowRoot | null {
|
||||
try {
|
||||
if (this.isChromeValue) {
|
||||
return chrome.dom.openOrClosedShadowRoot(el);
|
||||
}
|
||||
return el.openOrClosedShadowRoot();
|
||||
} catch (e) {
|
||||
fnConsoleLog('BrowserApiWrapper->shadowRoot->ERROR', el, e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static sendTabMessage = <T>(msg: BusMessage<T>): Promise<void> => {
|
||||
return new Promise((resolve: (...arg: any) => void, reject: (...arg: any) => void) => {
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-floating-promises */
|
||||
this.tabs.query({ active: true, currentWindow: true }, (tabs: chrome.tabs.Tab[]) => {
|
||||
const currentTab: BrowserTabObject | undefined = tabs[0];
|
||||
if (currentTab?.id) {
|
||||
try {
|
||||
this.tabs.sendMessage(currentTab.id, msg, resolve);
|
||||
} catch (e) {
|
||||
fnConsoleLog('Error sendTabMessage', msg, e, 'lastError', BrowserApi.runtime.lastError);
|
||||
reject(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
static sendRuntimeMessage = async <T>(msg: BusMessage<T>): Promise<void> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
this.runtime.sendMessage(msg, (ack: any) => {
|
||||
if (Config.showAckMessage) fnConsoleLog(`${msg.type}->ack`);
|
||||
resolve(ack);
|
||||
});
|
||||
} catch (e) {
|
||||
fnConsoleLog('runtime.lastError', msg, e, 'lastError', BrowserApi.runtime.lastError);
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* This file is part of the pinmenote-extension distribution (https://github.com/pinmenote/pinmenote-extension).
|
||||
* Copyright (c) 2023 Michal Szczepanski.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { BrowserApi } from './browser.api';
|
||||
|
||||
export class BrowserStorage {
|
||||
static async get<T>(key: string): Promise<T> {
|
||||
const value = await BrowserApi.localStore.get(key);
|
||||
return value[key];
|
||||
}
|
||||
|
||||
static async getAll(): Promise<any> {
|
||||
return await BrowserApi.localStore.get();
|
||||
}
|
||||
|
||||
static async getBytesInUse(key?: string): Promise<number> {
|
||||
return await BrowserApi.localStore.getBytesInUse(key);
|
||||
}
|
||||
|
||||
static async set<T>(key: string, value: T): Promise<void> {
|
||||
const v: { [key: string]: any } = {};
|
||||
v[key] = value;
|
||||
await BrowserApi.localStore.set(v);
|
||||
}
|
||||
|
||||
static async remove(key: string): Promise<void> {
|
||||
await BrowserApi.localStore.remove(key);
|
||||
}
|
||||
|
||||
static async clear(): Promise<void> {
|
||||
await BrowserApi.localStore.clear();
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
export const Config = {
|
||||
isProduction: process.env.IS_PRODUCTION === 'true',
|
||||
foo: process.env.FOO
|
||||
showAckMessage: false
|
||||
};
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
import { Config } from './config';
|
||||
|
||||
export const fnConsoleLog = (...args: any[]) => {
|
||||
if (!Config.isProduction) {
|
||||
//eslint-disable-next-line no-console, @typescript-eslint/no-unsafe-argument
|
||||
console.log(...args);
|
||||
}
|
||||
};
|
|
@ -1,10 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Hello World</title>
|
||||
</head>
|
||||
<body>
|
||||
<script type="module" src="index.ts"></script>
|
||||
</body>
|
||||
</html>
|
14
src/index.ts
14
src/index.ts
|
@ -1,12 +1,2 @@
|
|||
import { Config } from './config';
|
||||
const h1 = document.createElement('h1');
|
||||
document.body.appendChild(h1);
|
||||
h1.innerText = `is production ${Config.isProduction.toString()}, foo value from .env: ${Config.foo || ''}`;
|
||||
|
||||
// Hot reloading
|
||||
if (!Config.isProduction) {
|
||||
const ws = new WebSocket('ws://localhost:1234');
|
||||
ws.onmessage = () => {
|
||||
window.location.reload();
|
||||
};
|
||||
}
|
||||
export * from './browser.api';
|
||||
export * from './browser.storage';
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"include": ["src/**/*"],
|
||||
"compilerOptions": {
|
||||
"target": "es2021",
|
||||
"strict": true,
|
||||
"types": ["node", "firefox-webext-browser", "chrome"],
|
||||
"typeRoots": ["node_modules/@types", "@types"]
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue