webext-tools
Advanced tools
| /** Wrapper around the dev tools' `eval` function to throw proper errors */ | ||
| export default function devToolsEval<T>(code: string): Promise<T>; |
| function printf(template, arguments_) { | ||
| // eslint-disable-next-line unicorn/no-array-reduce -- Short and already described by "printf" | ||
| return arguments_.reduce((message, part) => message.replace('%s', part), template); | ||
| } | ||
| /** Wrapper around the dev tools' `eval` function to throw proper errors */ | ||
| // eslint-disable-next-line unicorn/prevent-abbreviations | ||
| export default async function devToolsEval(code) { | ||
| return new Promise((resolve, reject) => { | ||
| chrome.devtools.inspectedWindow.eval(code, (response, error) => { | ||
| // Handle Dev Tools API error response | ||
| // https://developer.chrome.com/docs/extensions/reference/devtools_inspectedWindow/#method-eval | ||
| // https://github.com/pixiebrix/pixiebrix-extension/pull/999#discussion_r684370643 | ||
| if (error?.isError) { | ||
| reject(new Error(printf(error.description, error.details))); | ||
| return; | ||
| } | ||
| resolve(response); | ||
| }); | ||
| }); | ||
| } |
| type CleanPathname<T extends string> = T extends `${string}${'#' | '?'}${string}` ? never : T; | ||
| export type ExtensionUrlOptions = { | ||
| hash?: string; | ||
| searchParams?: Record<string, string>; | ||
| }; | ||
| export default function getExtensionUrl<T extends string>(pathname: CleanPathname<T>, options?: ExtensionUrlOptions): URL; | ||
| export {}; |
| export default function getExtensionUrl(pathname, options) { | ||
| const url = new URL(chrome.runtime.getURL(pathname)); | ||
| if (options?.hash) { | ||
| url.hash = options.hash; | ||
| } | ||
| if (options?.searchParams) { | ||
| for (const [key, value] of Object.entries(options.searchParams)) { | ||
| url.searchParams.set(key, value); | ||
| } | ||
| } | ||
| return url; | ||
| } |
| export default function queryTabsByUrl(matches: string[], excludeMatches?: string[]): Promise<number[]>; |
| import { patternToRegex } from 'webext-patterns'; | ||
| export default async function queryTabsByUrl(matches, excludeMatches) { | ||
| if (matches.length === 0) { | ||
| return []; | ||
| } | ||
| const exclude = excludeMatches ? patternToRegex(...excludeMatches) : undefined; | ||
| const tabs = await chrome.tabs.query({ url: matches }); | ||
| return tabs | ||
| .filter(tab => tab.id && tab.url && (exclude ? !exclude.test(tab.url) : true)) | ||
| .map(tab => tab.id); | ||
| } |
| export type ClickListener = (data: chrome.contextMenus.OnClickData, tab: chrome.tabs.Tab) => void; | ||
| export declare function createContextMenu(settings: chrome.contextMenus.CreateProperties & { | ||
| export default function createContextMenu(settings: Omit<chrome.contextMenus.CreateProperties, 'contexts'> & { | ||
| id: string; | ||
| contexts?: chrome.contextMenus.ContextType[]; | ||
| }): Promise<void>; |
@@ -20,3 +20,3 @@ import chromeP from 'webext-polyfill-kinda'; | ||
| } | ||
| export async function createContextMenu(settings) { | ||
| export default async function createContextMenu(settings) { | ||
| const { onclick, ...createSettings } = settings; | ||
@@ -23,0 +23,0 @@ if (!chrome.contextMenus) { |
@@ -1,1 +0,1 @@ | ||
| export declare function doesTabExist(tabId: number): Promise<boolean>; | ||
| export default function doesTabExist(tabId: number): Promise<boolean>; |
@@ -1,2 +0,1 @@ | ||
| import chromeP from 'webext-polyfill-kinda'; | ||
| /** Utility to await promises where you only care whether they throw or not */ | ||
@@ -12,4 +11,4 @@ async function isPromiseFulfilled(promise) { | ||
| } | ||
| export async function doesTabExist(tabId) { | ||
| return isPromiseFulfilled(chromeP.tabs.get(tabId)); | ||
| export default async function doesTabExist(tabId) { | ||
| return isPromiseFulfilled(chrome.tabs.get(tabId)); | ||
| } |
| import { type Target } from './target.js'; | ||
| export declare function getTabUrl(target: number | Target): Promise<string | undefined>; | ||
| export default function getTabUrl(target: number | Target): Promise<string | undefined>; |
@@ -1,9 +0,7 @@ | ||
| import chromeP from 'webext-polyfill-kinda'; | ||
| import { executeFunction } from 'webext-content-scripts'; | ||
| import { castTarget } from './target.js'; | ||
| export async function getTabUrl(target) { | ||
| import castTarget from './target.js'; | ||
| export default async function getTabUrl(target) { | ||
| const { frameId, tabId } = castTarget(target); | ||
| try { | ||
| if (frameId === 0 && 'tabs' in globalThis.chrome) { | ||
| const tab = await chromeP.tabs.get(tabId); | ||
| const tab = await chrome.tabs.get(tabId); | ||
| if (tab.url) { | ||
@@ -13,3 +11,7 @@ return tab.url; | ||
| } | ||
| return await executeFunction(target, () => location.href); | ||
| const [result] = await chrome.scripting.executeScript({ | ||
| target: { tabId, frameIds: [frameId] }, | ||
| func: () => location.href, | ||
| }); | ||
| return result?.result; | ||
| } | ||
@@ -16,0 +18,0 @@ catch { |
| export type GetPopupUrl = (tabUrl: string | undefined) => Promise<string | undefined> | string | undefined; | ||
| export declare function setActionPopup(getPopupUrl: GetPopupUrl): void; | ||
| export default function setActionPopup(getPopupUrl: GetPopupUrl): void; |
@@ -1,6 +0,4 @@ | ||
| import chromeP from 'webext-polyfill-kinda'; | ||
| import { getTabUrl } from './get-tab-url.js'; | ||
| import getTabUrl from './get-tab-url.js'; | ||
| async function rawSetPopup(getPopupUrl, tabId, url) { | ||
| const browserAction = chrome.action ?? chromeP.browserAction; | ||
| await browserAction.setPopup({ | ||
| await chrome.action.setPopup({ | ||
| popup: await getPopupUrl(url ?? await getTabUrl(tabId)) ?? '', | ||
@@ -10,3 +8,3 @@ tabId, | ||
| } | ||
| export function setActionPopup(getPopupUrl) { | ||
| export default function setActionPopup(getPopupUrl) { | ||
| const setOnActiveTab = async (windowId) => { | ||
@@ -16,3 +14,3 @@ if (windowId === chrome.windows.WINDOW_ID_NONE) { | ||
| } | ||
| const [tab] = await chromeP.tabs.query({ | ||
| const [tab] = await chrome.tabs.query({ | ||
| active: true, | ||
@@ -19,0 +17,0 @@ ...windowId ? { windowId } : { lastFocusedWindow: true }, |
@@ -5,2 +5,2 @@ export type Target = { | ||
| }; | ||
| export declare function castTarget(target: number | Target): Target; | ||
| export default function castTarget(target: number | Target): Target; |
@@ -1,2 +0,2 @@ | ||
| export function castTarget(target) { | ||
| export default function castTarget(target) { | ||
| return typeof target === 'object' | ||
@@ -3,0 +3,0 @@ ? target |
+6
-5
| { | ||
| "name": "webext-tools", | ||
| "version": "3.0.0", | ||
| "description": "Utility functions for Web Extensions, manifest v2 and v3", | ||
| "version": "4.0.0", | ||
| "description": "Utility functions for Web Extensions", | ||
| "keywords": [ | ||
@@ -21,4 +21,5 @@ "extension", | ||
| "type": "module", | ||
| "exports": "./distribution/index.js", | ||
| "types": "./distribution/index.d.ts", | ||
| "exports": { | ||
| "./*": "./distribution/*" | ||
| }, | ||
| "scripts": { | ||
@@ -46,4 +47,4 @@ "build": "tsc", | ||
| "dependencies": { | ||
| "webext-content-scripts": "^2.7.0", | ||
| "webext-detect": "^5.3.2", | ||
| "webext-patterns": "^1.5.1", | ||
| "webext-polyfill-kinda": "^1.0.2" | ||
@@ -50,0 +51,0 @@ }, |
+11
-3
@@ -6,3 +6,3 @@ # webext-tools [](https://www.npmjs.com/package/webext-tools) | ||
| - Browsers: Chrome, Firefox, and Safari | ||
| - Manifest: v2 and v3 | ||
| - Manifest: v3 | ||
@@ -21,8 +21,16 @@ **Sponsored by [PixieBrix](https://www.pixiebrix.com)** :tada: | ||
| This package exports various utilities, just import what you need. | ||
| This package exports various utilities. Each tool has its own entry point, so you only import what you need: | ||
| ```js | ||
| import doesTabExist from 'webext-tools/does-tab-exist.js'; | ||
| import getExtensionUrl from 'webext-tools/get-extension-url.js'; | ||
| ``` | ||
| - [doesTabExist](./source/does-tab-exist.md) - Checks whether a tab exists. | ||
| - [getExtensionUrl](./source/get-extension-url.md) - Generates a `URL` object for a resource bundled with the extension. | ||
| - [getTabUrl](./source/get-tab-url.md) - Get a tab or frame’s URL even with limited permissions. | ||
| - [queryTabsByUrl](./source/query-tabs-by-url.md) - Get the IDs of tabs matching URL patterns. | ||
| - [setActionPopup](./source/set-action-popup.md) - Sets the popup URL (or removes the popup) depending on the current tab. | ||
| - [createContextMenus](./source/create-context-menus.md) - Creates context menus without pain. | ||
| - [createContextMenu](./source/create-context-menu.md) - Creates context menus without pain. | ||
| - [devToolsEval](./source/dev-tools-eval.md) - Wrapper around the DevTools `eval` function that throws proper errors. | ||
| - `addOptionsContextMenu` was moved to [webext-bugs](https://github.com/fregante/webext-bugs). | ||
@@ -29,0 +37,0 @@ |
| export * from './does-tab-exist.js'; | ||
| export * from './get-tab-url.js'; | ||
| export * from './set-action-popup.js'; | ||
| export * from './create-context-menu.js'; | ||
| export * from './target.js'; |
| export * from './does-tab-exist.js'; | ||
| export * from './get-tab-url.js'; | ||
| export * from './set-action-popup.js'; | ||
| export * from './create-context-menu.js'; | ||
| export * from './target.js'; |
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
13198
22.75%21
23.53%204
26.71%47
20.51%1
Infinity%+ Added
- Removed
- Removed