Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: CI

on:
push:
pull_request:

permissions:
contents: read

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm

- run: npm ci

- name: Jest
run: npx jest

- name: Tool tests
run: npm run test:tools

- name: Build
run: npm run build

- name: Permission lock and version gate
run: npm run lock

- name: Secret scan
run: npm run scan:secrets

- name: Store zip gates
run: node tools/zip.mjs

- uses: actions/upload-artifact@v4
with:
name: store-zip
path: dist-zips/*.zip
if-no-files-found: error
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ coverage/
dist/
build/
*.zip
dist-zips/
16 changes: 12 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,14 +125,22 @@ See [docs/PRICE_SPREAD_ANALYSIS.md](docs/PRICE_SPREAD_ANALYSIS.md) for the full

## Installation

1. Clone the repository:
1. Clone the repository and build it:
```bash
git clone https://github.com/enesyilmaz7/AmazonSellerScraper.git
git clone https://github.com/EnesYilmazcode/AmazonSellerScraper.git
cd AmazonSellerScraper
npm ci
npm run build
```
2. Open Chrome and navigate to `chrome://extensions/`
3. Enable **Developer mode** (top-right toggle)
4. Click **Load unpacked** and select the project folder
5. Click the ProScan extension icon → **Settings** → paste your [Gemini API key](https://aistudio.google.com/apikey) (free)
4. Click **Load unpacked** and select the `dist/` folder. The repo root does not load on its own, because the service worker has to be bundled.

For local Firebase work, `npm run build:dev` points the build at the emulators under the `demo-proscan` project.

## Release checks

`npm test` runs the Jest suite and the tool tests. `npm run check` builds, then runs the permission lock (nothing may be added over `tools/live-manifest.json`, the published v2.0 manifest), the version gate and the secret scan. `npm run zip` writes the store package to `dist-zips/` and refuses a dev build, a stray file, uncommitted changes (`node tools/zip.mjs --allow-dirty` overrides that for local tries), or any gate failure. CI runs all of these.

## Usage

Expand Down
111 changes: 52 additions & 59 deletions manifest.json
Original file line number Diff line number Diff line change
@@ -1,59 +1,52 @@
{
"manifest_version": 3,
"name": "ProScan - Amazon Product Scraper",
"version": "2.0",
"description": "Scrape Amazon seller products with analytics and AI-powered product insights",
"permissions": [
"storage",
"downloads"
],
"host_permissions": [
"*://*.amazon.com/*",
"https://generativelanguage.googleapis.com/*",
"https://identitytoolkit.googleapis.com/*",
"https://securetoken.googleapis.com/*",
"https://firestore.googleapis.com/*",
"https://www.googleapis.com/*",
"https://*.firebaseapp.com/*",
"http://127.0.0.1/*",
"http://localhost/*"
],
"action": {
"default_popup": "popup/popup.html",
"default_icon": {
"16": "assets/icons/icon16.png",
"48": "assets/icons/icon48.png",
"128": "assets/icons/icon128.png"
}
},
"icons": {
"16": "assets/icons/icon16.png",
"48": "assets/icons/icon48.png",
"128": "assets/icons/icon128.png"
},
"content_scripts": [
{
"matches": ["*://*.amazon.com/*"],
"js": [
"scripts/modules/price.js",
"scripts/modules/delta.js",
"scripts/content/scraper.js",
"scripts/content/chatbot.js",
"scripts/content/offer-fetcher.js"
],
"run_at": "document_idle"
}
],
"content_security_policy": {
"extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'; connect-src 'self' https://identitytoolkit.googleapis.com https://securetoken.googleapis.com https://firestore.googleapis.com https://www.googleapis.com https://*.firebaseapp.com http://127.0.0.1:* http://localhost:*"
},
"background": {
"service_worker": "scripts/background/service-worker.js"
},
"web_accessible_resources": [
{
"resources": ["styles/chatbot.css", "libs/*.js", "assets/icons/*.png"],
"matches": ["*://*.amazon.com/*"]
}
]
}
{
"manifest_version": 3,
"name": "ProScan - Amazon Product Scraper",
"version": "2.1.0",
"description": "Scrape Amazon seller products with analytics and AI-powered product insights",
"permissions": [
"storage",
"downloads"
],
"host_permissions": [
"*://*.amazon.com/*",
"https://generativelanguage.googleapis.com/*"
],
"action": {
"default_popup": "popup/popup.html",
"default_icon": {
"16": "assets/icons/icon16.png",
"48": "assets/icons/icon48.png",
"128": "assets/icons/icon128.png"
}
},
"icons": {
"16": "assets/icons/icon16.png",
"48": "assets/icons/icon48.png",
"128": "assets/icons/icon128.png"
},
"content_scripts": [
{
"matches": ["*://*.amazon.com/*"],
"js": [
"scripts/modules/price.js",
"scripts/modules/delta.js",
"scripts/content/scraper.js",
"scripts/content/chatbot.js",
"scripts/content/offer-fetcher.js"
],
"run_at": "document_idle"
}
],
"content_security_policy": {
"extension_pages": "script-src 'self'; object-src 'self'; connect-src 'self' https://identitytoolkit.googleapis.com https://securetoken.googleapis.com https://firestore.googleapis.com https://generativelanguage.googleapis.com"
},
"background": {
"service_worker": "scripts/background/service-worker.js"
},
"web_accessible_resources": [
{
"resources": ["styles/chatbot.css", "libs/*.js", "assets/icons/*.png"],
"matches": ["*://*.amazon.com/*"]
}
]
}
9 changes: 7 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,16 @@
"private": true,
"scripts": {
"build": "node tools/build.mjs",
"build:dev": "node tools/build.mjs --dev",
"zip": "node tools/build.mjs && node tools/zip.mjs",
"test": "jest --verbose",
"test": "jest --verbose && npm run test:tools",
"test:unit": "jest tests/unit --verbose",
"test:integration": "jest tests/integration --verbose",
"test:coverage": "jest --coverage"
"test:tools": "node --test --test-concurrency=1 \"tools/tests/*.test.mjs\"",
"test:coverage": "jest --coverage",
"lock": "node tools/permission-lock.mjs && node tools/version-gate.mjs",
"scan:secrets": "node tools/secret-scan.mjs",
"check": "npm run build && npm run lock && npm run scan:secrets"
},
"devDependencies": {
"adm-zip": "^0.5.17",
Expand Down
35 changes: 24 additions & 11 deletions scripts/background/firebase-config.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
/**
* @fileoverview Firebase config for the ProScan extension.
*
* Same Firebase project (proscanbot) for dev and prod — only the emulator
* wiring differs, gated by PROSCAN_ENV which esbuild inlines at build time
* (see tools/build.mjs `define`). Web API keys are public identifiers, not
* secrets; access is governed entirely by Firestore security rules.
* Prod talks to the proscanbot project. The dev build
* (`PROSCAN_ENV=dev npm run build`) sets the literal __PROSCAN_EMULATOR__
* through esbuild `define` and points at the local emulator suite under the
* demo-proscan project, the same id the dashboard dev build uses. Web API
* keys are public identifiers, not secrets; access is governed by the
* Firestore security rules.
*
* @module FirebaseConfig
*/

export const FIREBASE_CONFIG = {
/* global __PROSCAN_EMULATOR__ */
export const USE_EMULATOR =
typeof __PROSCAN_EMULATOR__ !== 'undefined' && __PROSCAN_EMULATOR__ === true;

const PROD_CONFIG = {
apiKey: 'AIzaSyAp0HrcvFwpMxrlqbxa9xjUvwGoTa7QpUU',
authDomain: 'proscanbot.firebaseapp.com',
projectId: 'proscanbot',
Expand All @@ -18,10 +24,17 @@ export const FIREBASE_CONFIG = {
messagingSenderId: '886322190589',
};

// Build dev with `PROSCAN_ENV=dev npm run build` to point at the local emulator.
export const USE_EMULATOR =
typeof process !== 'undefined' &&
process.env &&
process.env.PROSCAN_ENV === 'dev';
// demo-* projects only ever talk to the emulators. Written inline so the
// prod build folds them away.
export const FIREBASE_CONFIG = USE_EMULATOR
? {
...PROD_CONFIG,
authDomain: 'demo-proscan.firebaseapp.com',
projectId: 'demo-proscan',
storageBucket: 'demo-proscan.appspot.com',
}
: PROD_CONFIG;

export const EMULATOR = { host: '127.0.0.1', authPort: 9099, firestorePort: 8080 };
export const EMULATOR = USE_EMULATOR
? { host: '127.0.0.1', authPort: 9099, firestorePort: 8080 }
: null;
25 changes: 6 additions & 19 deletions scripts/background/service-worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,6 @@ import { syncToCloud } from './sync.js';
/** @const {string} Gemini API endpoint for content generation */
const GEMINI_API_URL = 'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent';

/**
* Fallback API key (base64-encoded). Used only when the user hasn't
* configured their own key via the popup settings panel.
* @const {string}
* @private
*/
const _t = 'QUl6YVN5RHdfOVhQLXRpQ0tLX3lkQThCd0ZrZUpxNWdTdTAxNUhj';

/**
* Decode the fallback API key.
* @returns {string} Decoded API key
* @private
*/
const _dk = () => atob(_t);

/**
* Main message listener -- routes messages between extension components.
*
Expand Down Expand Up @@ -74,17 +59,19 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
* Handle a chat message by calling the Gemini 2.0 Flash API.
*
* Builds a prompt with the system role, product context, and user question.
* Uses the user's API key from chrome.storage if available, otherwise
* falls back to the built-in key.
* Uses the user's API key from chrome.storage. There is no built-in key.
*
* @param {string} question - User's natural language question
* @param {Object[]} products - Array of product objects for context
* @returns {Promise<string>} AI-generated response text
* @throws {Error} On invalid API key or Gemini API failure
* @throws {Error} On a missing or invalid API key, or Gemini API failure
*/
async function handleChatMessage(question, products) {
const data = await chrome.storage.local.get(['geminiApiKey']);
const apiKey = data.geminiApiKey || _dk();
const apiKey = data.geminiApiKey;
if (!apiKey) {
throw new Error('AI chat needs a Gemini API key, and none is set.');
}

const productCount = products.length;
const productList = products.map(p =>
Expand Down
Loading
Loading