'],
+ },
+ ];
+
+ // add sidepanel
+ if (isFirefox) {
+ manifest.sidebar_action = {
+ default_panel: 'dist/sidepanel/index.html',
+ };
+ } else {
+ // the sidebar_action does not work for chromium based
+ (manifest as any).side_panel = {
+ default_path: 'dist/sidepanel/index.html',
+ };
+ manifest.permissions?.push('sidePanel');
+ }
+
+ return manifest;
+}
export async function writeManifest() {
- await fs.writeJSON(r('extension/manifest.json'), await getManifest(), {
+ await fs.writeJSON(r(`${outputDir}/manifest.json`), await getManifest(), {
spaces: 2,
});
log('PRE', 'write manifest.json');
diff --git a/scripts/prepare.ts b/scripts/prepare.ts
index 3bc062b..ee1995e 100644
--- a/scripts/prepare.ts
+++ b/scripts/prepare.ts
@@ -2,7 +2,7 @@
import { execSync } from 'node:child_process';
import fs from 'fs-extra';
import chokidar from 'chokidar';
-import { isDev, log, port, r } from './utils';
+import { isDev, log, outputDir, port, r } from './utils';
/**
* Stub index.html to use Vite in development
@@ -11,12 +11,12 @@ async function stubIndexHtml() {
const views = ['sidepanel', 'options'];
for (const view of views) {
- await fs.ensureDir(r(`extension/dist/${view}`));
+ await fs.ensureDir(r(`${outputDir}/dist/${view}`));
let data = await fs.readFile(r(`src/${view}/index.html`), 'utf-8');
data = data
.replace('"./main.ts"', `"http://localhost:${port}/${view}/main.ts"`)
.replace('', 'Vite server did not start
');
- await fs.writeFile(r(`extension/dist/${view}/index.html`), data, 'utf-8');
+ await fs.writeFile(r(`${outputDir}/dist/${view}/index.html`), data, 'utf-8');
log('PRE', `stub ${view}`);
}
}
@@ -32,7 +32,7 @@ if (isDev) {
chokidar.watch(r('src/**/*.html')).on('change', () => {
stubIndexHtml();
});
- chokidar.watch([r('src/manifest.ts'), r('package.json')]).on('change', () => {
+ chokidar.watch([r('scripts/manifest.ts'), r('package.json')]).on('change', () => {
writeManifest();
});
}
diff --git a/scripts/utils.ts b/scripts/utils.ts
index 679a09c..e6f0f11 100644
--- a/scripts/utils.ts
+++ b/scripts/utils.ts
@@ -6,6 +6,7 @@ export const port = Number(process.env.PORT || '') || 3303;
export const r = (...args: string[]) => resolve(__dirname, '..', ...args);
export const isDev = process.env.NODE_ENV !== 'production';
export const isFirefox = process.env.EXTENSION === 'firefox';
+export const outputDir = isFirefox ? 'extension-firefox' : 'extension';
export function log(name: string, message: string) {
console.log(black(bgCyan(` ${name} `)), message);
diff --git a/src/background/main.ts b/src/background/main.ts
index 271f8ed..dbe4f85 100644
--- a/src/background/main.ts
+++ b/src/background/main.ts
@@ -1,3 +1,5 @@
+import { isFirefox } from '~/env';
+
// https://github.com/serversideup/webext-bridge/issues/67#issuecomment-2676094094
import('webext-bridge/background');
@@ -13,22 +15,21 @@ if (import.meta.hot) {
const USE_SIDE_PANEL = true;
// to toggle the sidepanel with the action button in chromium:
-if (USE_SIDE_PANEL) {
- // @ts-expect-error missing types
- browser.sidePanel
- .setPanelBehavior({ openPanelOnActionClick: true })
- .catch((error: unknown) => console.error(error));
+if (USE_SIDE_PANEL && !isFirefox) {
+ (browser as unknown as Chrome).sidePanel?.setPanelBehavior({ openPanelOnActionClick: true });
}
browser.runtime.onInstalled.addListener(() => {
// eslint-disable-next-line no-console
console.log('Azon Seeker installed');
- browser.contextMenus.create({
- id: 'show-result',
- title: '结果页',
- contexts: ['action'],
- });
+ if (USE_SIDE_PANEL && !isFirefox) {
+ browser.contextMenus.create({
+ id: 'show-result',
+ title: '结果页',
+ contexts: ['action'],
+ });
+ }
});
browser.contextMenus.onClicked.addListener((info) => {
diff --git a/src/env.ts b/src/env.ts
index 22128e7..ea86683 100644
--- a/src/env.ts
+++ b/src/env.ts
@@ -4,6 +4,7 @@ const forbiddenProtocols = [
'chrome://',
'devtools://',
'edge://',
+ 'moz-extension://',
'https://chrome.google.com/webstore',
];
diff --git a/src/global.d.ts b/src/global.d.ts
index a5ea335..2ae7115 100644
--- a/src/global.d.ts
+++ b/src/global.d.ts
@@ -9,6 +9,25 @@ declare module '*.vue' {
export default component;
}
+declare interface Chrome {
+ sidePanel?: {
+ setPanelBehavior: (options: { openPanelOnActionClick: boolean }) => void;
+ setOptions: (options: { path?: string }) => void;
+ onShown: {
+ addListener: (callback: () => void) => void;
+ removeListener: (callback: () => void) => void;
+ hasListener: (callback: () => void) => boolean;
+ };
+ onHidden: {
+ addListener: (callback: () => void) => void;
+ removeListener: (callback: () => void) => void;
+ hasListener: (callback: () => void) => boolean;
+ };
+ // V3 还支持指定页面的侧边栏配置
+ getOptions: (options: { tabId?: number }) => Promise<{ path?: string }>;
+ };
+}
+
declare type AmazonSearchItem = {
keywords: string;
page: number;
diff --git a/src/logic/execute-script.ts b/src/logic/execute-script.ts
index 0710cc6..c03411c 100644
--- a/src/logic/execute-script.ts
+++ b/src/logic/execute-script.ts
@@ -1,3 +1,5 @@
+import { isFirefox } from '~/env';
+
/**
* Executes a provided asynchronous function in the context of a specific browser tab.
* @param tabId - The ID of the browser tab where the script will be executed.
@@ -45,6 +47,15 @@ export async function exec>(
): Promise {
const { timeout = 30000 } = options;
return new Promise(async (resolve, reject) => {
+ if (isFirefox) {
+ while (true) {
+ const tab = await browser.tabs.get(tabId);
+ if (tab.status === 'complete') {
+ break;
+ }
+ await new Promise((r) => setTimeout(r, 100));
+ }
+ }
setTimeout(() => reject('脚本运行超时'), timeout);
try {
const injectResults = await browser.scripting.executeScript({
diff --git a/src/manifest.ts b/src/manifest.ts
deleted file mode 100644
index f97a72a..0000000
--- a/src/manifest.ts
+++ /dev/null
@@ -1,79 +0,0 @@
-import fs from 'fs-extra';
-import { type Manifest } from 'webextension-polyfill';
-import type PkgType from '../package.json';
-import { isDev, isFirefox, port, r } from '../scripts/utils';
-
-export async function getManifest() {
- const pkg = (await fs.readJSON(r('package.json'))) as typeof PkgType;
-
- // update this file to update this manifest.json
- // can also be conditional based on your need
- const manifest: Manifest.WebExtensionManifest = {
- manifest_version: 3,
- name: pkg.displayName || pkg.name,
- version: pkg.version,
- description: pkg.description,
- action: {
- default_icon: './assets/icon-512.png',
- },
- options_ui: {
- page: './dist/options/index.html',
- open_in_tab: true,
- },
- background: isFirefox
- ? {
- scripts: ['dist/background/index.mjs'],
- type: 'module',
- }
- : {
- service_worker: './dist/background/index.mjs',
- },
- icons: {
- 16: './assets/icon-512.png',
- 48: './assets/icon-512.png',
- 128: './assets/icon-512.png',
- },
- permissions: [
- 'tabs',
- 'storage',
- 'activeTab',
- 'sidePanel',
- 'scripting',
- 'unlimitedStorage',
- 'contextMenus',
- ],
- host_permissions: ['*://*/*'],
- content_scripts: [
- {
- matches: [''],
- js: ['dist/contentScripts/index.global.js'],
- },
- ],
- web_accessible_resources: [
- {
- resources: ['dist/contentScripts/index.css'],
- matches: [''],
- },
- ],
- content_security_policy: {
- extension_pages: isDev
- ? // this is required on dev for Vite script to load
- `script-src \'self\' http://localhost:${port}; object-src \'self\'`
- : "script-src 'self'; object-src 'self'",
- },
- };
-
- // add sidepanel
- if (isFirefox) {
- manifest.sidebar_action = {
- default_panel: 'dist/sidepanel/index.html',
- };
- } else {
- // the sidebar_action does not work for chromium based
- (manifest as any).side_panel = {
- default_path: 'dist/sidepanel/index.html',
- };
- }
-
- return manifest;
-}
diff --git a/vite.config.background.mts b/vite.config.background.mts
index 502f1df..575e0de 100644
--- a/vite.config.background.mts
+++ b/vite.config.background.mts
@@ -1,6 +1,6 @@
import { defineConfig } from 'vite';
import { sharedConfig } from './vite.config.mjs';
-import { isDev, r } from './scripts/utils';
+import { isDev, outputDir, r } from './scripts/utils';
import packageJson from './package.json';
// bundling the content script using Vite
@@ -11,13 +11,11 @@ export default defineConfig({
__NAME__: JSON.stringify(packageJson.name),
// https://github.com/vitejs/vite/issues/9320
// https://github.com/vitejs/vite/issues/9186
- 'process.env.NODE_ENV': JSON.stringify(
- isDev ? 'development' : 'production',
- ),
+ 'process.env.NODE_ENV': JSON.stringify(isDev ? 'development' : 'production'),
},
build: {
watch: isDev ? {} : undefined,
- outDir: r('extension/dist/background'),
+ outDir: r(`${outputDir}/dist/background`),
cssCodeSplit: false,
emptyOutDir: false,
sourcemap: isDev ? 'inline' : false,
diff --git a/vite.config.content.mts b/vite.config.content.mts
index 19f3aac..3f3b9ed 100644
--- a/vite.config.content.mts
+++ b/vite.config.content.mts
@@ -1,6 +1,6 @@
import { defineConfig } from 'vite';
import { sharedConfig } from './vite.config.mjs';
-import { isDev, r } from './scripts/utils';
+import { isDev, outputDir, r } from './scripts/utils';
import packageJson from './package.json';
// bundling the content script using Vite
@@ -11,13 +11,11 @@ export default defineConfig({
__NAME__: JSON.stringify(packageJson.name),
// https://github.com/vitejs/vite/issues/9320
// https://github.com/vitejs/vite/issues/9186
- 'process.env.NODE_ENV': JSON.stringify(
- isDev ? 'development' : 'production',
- ),
+ 'process.env.NODE_ENV': JSON.stringify(isDev ? 'development' : 'production'),
},
build: {
watch: isDev ? {} : undefined,
- outDir: r('extension/dist/contentScripts'),
+ outDir: r(`${outputDir}/dist/contentScripts`),
cssCodeSplit: false,
emptyOutDir: false,
sourcemap: isDev ? 'inline' : false,
diff --git a/vite.config.mts b/vite.config.mts
index 1ee9722..143d825 100644
--- a/vite.config.mts
+++ b/vite.config.mts
@@ -11,7 +11,7 @@ import Components from 'unplugin-vue-components/vite';
import AutoImport from 'unplugin-auto-import/vite';
import { NaiveUiResolver } from 'unplugin-vue-components/resolvers';
-import { isDev, port, r } from './scripts/utils';
+import { isDev, outputDir, port, r } from './scripts/utils';
import packageJson from './package.json';
export const sharedConfig: UserConfig = {
@@ -87,16 +87,18 @@ export default defineConfig(({ command }) => ({
host: 'localhost',
},
origin: `http://localhost:${port}`,
+ cors: { origin: [/moz-extension:\/\/.+/] },
},
build: {
watch: isDev ? {} : undefined,
- outDir: r('extension/dist'),
+ outDir: r(`${outputDir}/dist`),
emptyOutDir: false,
sourcemap: isDev ? 'inline' : false,
// https://developer.chrome.com/docs/webstore/program_policies/#:~:text=Code%20Readability%20Requirements
terserOptions: {
mangle: false,
},
+ chunkSizeWarningLimit: 1024, // 1MB
rollupOptions: {
input: {
sidepanel: r('src/sidepanel/index.html'),