diff --git a/.vscode/launch.json b/.vscode/launch.json
index 9e22072..3cb6354 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -10,7 +10,7 @@
"name": "Attach to side panel",
"webRoot": "${workspaceFolder}/src/",
"port": 9222,
- "urlFilter": "chrome-extension://*"
+ "urlFilter": "chrome-extension://fmalpbpehdilmjhnanhpjmnkgbahopfj/*"
}
]
}
diff --git a/scripts/prepare.ts b/scripts/prepare.ts
index 5e74961..c547d04 100644
--- a/scripts/prepare.ts
+++ b/scripts/prepare.ts
@@ -8,17 +8,14 @@ import { isDev, log, port, r } from './utils';
* Stub index.html to use Vite in development
*/
async function stubIndexHtml() {
- const views = ['options', 'popup', 'sidepanel'];
+ const views = ['sidepanel'];
for (const view of views) {
await fs.ensureDir(r(`extension/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
',
- );
+ .replace('', 'Vite server did not start
');
await fs.writeFile(r(`extension/dist/${view}/index.html`), data, 'utf-8');
log('PRE', `stub ${view}`);
}
diff --git a/shim.d.ts b/shim.d.ts
index c7dabe1..5019d15 100644
--- a/shim.d.ts
+++ b/shim.d.ts
@@ -4,6 +4,5 @@ declare module 'webext-bridge' {
export interface ProtocolMap {
// define message protocol types
// see https://github.com/antfu/webext-bridge#type-safe-protocols
- 'tab-update': { tabId: number };
}
}
diff --git a/src/contentScripts/index.ts b/src/contentScripts/index.ts
index 4637622..d7a5ebd 100644
--- a/src/contentScripts/index.ts
+++ b/src/contentScripts/index.ts
@@ -2,23 +2,20 @@
import App from './views/App.vue';
import { setupApp } from '~/logic/common-setup';
-// 是否挂在ContentScript Vue APP
+// 是否在ContentScript挂载Vue APP
const MOUNT_COMPONENT = false;
+/**
+ * mount component to context window
+ */
const mountComponent = () => {
- // mount component to context window
const container = document.createElement('div');
container.id = __NAME__;
const root = document.createElement('div');
const styleEl = document.createElement('link');
- const shadowDOM =
- container.attachShadow?.({ mode: __DEV__ ? 'open' : 'closed' }) ||
- container;
+ const shadowDOM = container.attachShadow?.({ mode: __DEV__ ? 'open' : 'closed' }) || container;
styleEl.setAttribute('rel', 'stylesheet');
- styleEl.setAttribute(
- 'href',
- browser.runtime.getURL('dist/contentScripts/index.css'),
- );
+ styleEl.setAttribute('href', browser.runtime.getURL('dist/contentScripts/index.css'));
shadowDOM.appendChild(styleEl);
shadowDOM.appendChild(root);
document.body.appendChild(container);
@@ -33,4 +30,3 @@ const mountComponent = () => {
mountComponent();
}
})();
-
diff --git a/src/logic/common-setup.ts b/src/logic/common-setup.ts
index 7d98d66..cbe4b58 100644
--- a/src/logic/common-setup.ts
+++ b/src/logic/common-setup.ts
@@ -1,5 +1,9 @@
import type { App } from 'vue';
+/**
+ * Setup Vue app
+ * @param app Vue app
+ */
export function setupApp(app: App) {
// Inject a globally available `$app` object in template
app.config.globalProperties.$app = {
diff --git a/src/logic/execute-script.ts b/src/logic/execute-script.ts
index 91f6197..8ce7299 100644
--- a/src/logic/execute-script.ts
+++ b/src/logic/execute-script.ts
@@ -1,13 +1,41 @@
/**
- * Execute Script on Document
- * @param tabId
- * @param func
- * @returns
+ * 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.
+ * @param func - The asynchronous function to execute in the tab's context. This function
+ * should be serializable and must not rely on external closures.
+ * @param payload - An optional payload object to pass as an argument to the executed function.
+ *
+ * @returns A promise that resolves to the result of the executed function, or `null` if an error occurs.
+ *
+ * @throws This function does not throw directly but logs an error to the console if the script injection fails.
+ *
+ * @example
+ * ```typescript
+ * const result = await exec(
+ * tabId,
+ * async (payload) => {
+ * return payload?.value ?? 0;
+ * },
+ * { value: 42 }
+ * );
+ * console.log(result); // Outputs: 42
+ * ```
*/
-export async function exec(tabId: number, func: () => Promise): Promise {
+export async function exec(tabId: number, func: () => Promise): Promise;
+export async function exec>(
+ tabId: number,
+ func: (payload: P) => Promise,
+ payload: P,
+): Promise;
+export async function exec>(
+ tabId: number,
+ func: (payload?: P) => Promise,
+ payload?: P,
+): Promise {
const injectResults = await browser.scripting.executeScript({
target: { tabId },
func,
+ args: payload ? [payload] : undefined,
});
const ret = injectResults.pop();
if (ret?.error) {
diff --git a/src/logic/index.ts b/src/logic/index.ts
deleted file mode 100644
index 8b13789..0000000
--- a/src/logic/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/src/logic/page-worker/index.ts b/src/logic/page-worker/index.ts
index 1f2cfed..374bb27 100644
--- a/src/logic/page-worker/index.ts
+++ b/src/logic/page-worker/index.ts
@@ -1,6 +1,6 @@
import Emittery from 'emittery';
import type { AmazonGoodsLinkItem, AmazonPageWorker, AmazonPageWorkerEvents } from './types';
-import Browser from 'webextension-polyfill';
+import type { Tabs } from 'webextension-polyfill';
import { exec } from '../execute-script';
/**
@@ -19,7 +19,7 @@ class AmazonPageWorkerImpl implements AmazonPageWorker {
readonly channel = new Emittery();
- private async getCurrentTab(): Promise {
+ private async getCurrentTab(): Promise {
const tab = await browser.tabs
.query({ active: true, currentWindow: true })
.then((tabs) => tabs[0]);
@@ -41,7 +41,7 @@ class AmazonPageWorkerImpl implements AmazonPageWorker {
return url.toString();
}
- private async wanderSearchSinglePage(tab: Browser.Tabs.Tab) {
+ private async wanderSearchSinglePage(tab: Tabs.Tab) {
const tabId = tab.id!;
// #region Wait for the Next button to appear, indicating that the product items have finished loading
await exec(tabId, async () => {
@@ -134,7 +134,7 @@ class AmazonPageWorkerImpl implements AmazonPageWorker {
return { data, hasNextPage };
}
- public async wanderSearchList(): Promise {
+ public async wanderSearchPage(): Promise {
const tab = await this.getCurrentTab();
let stopSignal = false;
const stop = async (_: unknown): Promise => {
@@ -240,6 +240,23 @@ class AmazonPageWorkerImpl implements AmazonPageWorker {
});
}
//#endregion
+ //#region Fetch Goods' Images
+ const imageUrls = await exec(tab.id!, async () => {
+ const node = document.evaluate(
+ `//div[@id='imgTagWrapperId']/img`,
+ document,
+ null,
+ XPathResult.FIRST_ORDERED_NODE_TYPE,
+ null,
+ ).singleNodeValue as HTMLImageElement | null;
+ return node ? [node.getAttribute('src')!] : null;
+ });
+ imageUrls &&
+ this.channel.emit('item-images-collected', {
+ asin,
+ urls: imageUrls,
+ });
+ //#endregion
}
}
diff --git a/src/logic/page-worker/types.d.ts b/src/logic/page-worker/types.d.ts
index 010fc9b..c6ceef3 100644
--- a/src/logic/page-worker/types.d.ts
+++ b/src/logic/page-worker/types.d.ts
@@ -26,6 +26,14 @@ interface AmazonPageWorkerEvents {
category2?: { name: string; rank: number };
};
+ /**
+ * The event is fired when images collected
+ */
+ ['item-images-collected']: {
+ asin: string;
+ urls: string[];
+ };
+
/**
* Error event that occurs when there is an issue with the Amazon page worker.
*/
@@ -49,7 +57,7 @@ interface AmazonPageWorker {
/**
* Browsing goods search page and collect links to those goods.
*/
- wanderSearchList(): Promise;
+ wanderSearchPage(): Promise;
/**
* Browsing goods detail page and collect target information.
diff --git a/src/manifest.ts b/src/manifest.ts
index e626f87..6bb2184 100644
--- a/src/manifest.ts
+++ b/src/manifest.ts
@@ -15,11 +15,6 @@ export async function getManifest() {
description: pkg.description,
action: {
default_icon: './assets/icon-512.png',
- default_popup: './dist/popup/index.html',
- },
- options_ui: {
- page: './dist/options/index.html',
- open_in_tab: true,
},
background: isFirefox
? {
diff --git a/src/options/Options.vue b/src/options/Options.vue
deleted file mode 100644
index 29b5196..0000000
--- a/src/options/Options.vue
+++ /dev/null
@@ -1,39 +0,0 @@
-
-
-
-
-
- Options Page
-
-
-
-
-
-
diff --git a/src/options/index.html b/src/options/index.html
deleted file mode 100644
index 72ab198..0000000
--- a/src/options/index.html
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
- Options
-
-
-
-
-
-
diff --git a/src/options/main.ts b/src/options/main.ts
deleted file mode 100644
index a1aa667..0000000
--- a/src/options/main.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { createApp } from 'vue';
-import App from './Options.vue';
-import { setupApp } from '~/logic/common-setup';
-import '../styles';
-
-const app = createApp(App);
-setupApp(app);
-app.mount('#app');
diff --git a/src/popup/Popup.vue b/src/popup/Popup.vue
deleted file mode 100644
index 1614abf..0000000
--- a/src/popup/Popup.vue
+++ /dev/null
@@ -1,43 +0,0 @@
-
-
-
-
-
-
-
diff --git a/src/popup/index.html b/src/popup/index.html
deleted file mode 100644
index 28ab624..0000000
--- a/src/popup/index.html
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
- Popup
-
-
-
-
-
-
diff --git a/src/popup/main.ts b/src/popup/main.ts
deleted file mode 100644
index a9d6ea0..0000000
--- a/src/popup/main.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import App from './Popup.vue';
-import { setupApp } from '~/logic/common-setup';
-import '../styles';
-
-const app = createApp(App);
-setupApp(app);
-app.mount('#app');
diff --git a/src/sidepanel/Sidepanel.vue b/src/sidepanel/Sidepanel.vue
index f04cc57..845baa0 100644
--- a/src/sidepanel/Sidepanel.vue
+++ b/src/sidepanel/Sidepanel.vue
@@ -72,7 +72,7 @@ const onCollect = async () => {
message.error(msg);
});
await worker.doSearch(keywords.value);
- await worker.wanderSearchList();
+ await worker.wanderSearchPage();
message.info('完成');
};
diff --git a/vite.config.mts b/vite.config.mts
index b270fb0..14bfd29 100644
--- a/vite.config.mts
+++ b/vite.config.mts
@@ -94,8 +94,6 @@ export default defineConfig(({ command }) => ({
},
rollupOptions: {
input: {
- options: r('src/options/index.html'),
- popup: r('src/popup/index.html'),
sidepanel: r('src/sidepanel/index.html'),
},
},