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
Binary file modified bun.lockb
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,12 @@
"default_title": "Click to open panel",
"default_popup": "index.html"
},
"background": {
"service_worker": "background.js",
"type": "module"
},
"content_scripts": [
{
"js": [
"bridge.js"
],
"matches": [
"<all_urls>"
],
"js": ["bridge.js"],
"matches": ["<all_urls>"],
"run_at": "document_start"
}
],
"permissions": [
"storage",
"activeTab",
"scripting"
]
"permissions": ["storage", "activeTab", "scripting"]
}
6 changes: 6 additions & 0 deletions packages/chrome/manifest.chrome.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"background": {
"service_worker": "background.js",
"type": "module"
}
}
12 changes: 12 additions & 0 deletions packages/chrome/manifest.firefox.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"background": {
"scripts": ["background.js"],
"type": "module"
},
"browser_specific_settings": {
"gecko": {
"id": "openbanker@voidranjer",
"strict_min_version": "109.0"
}
}
}
16 changes: 11 additions & 5 deletions packages/chrome/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,20 @@
],
"type": "module",
"scripts": {
"dev": "tsc --watch & vite build --watch",
"build": "tsc && vite build && node scripts/copy-frontend.js"
"dev": "cross-env TARGET=chrome tsc --watch & vite build --watch",
"build": "tsc && npm run build:chrome && npm run build:firefox",
"build:chrome": "cross-env TARGET=chrome vite build && node scripts/copy-frontend.js chrome",
"build:firefox": "cross-env TARGET=firefox vite build && node scripts/copy-frontend.js firefox"
},
"devDependencies": {
"@openbanker/core": "workspace:*",
"@openbanker/frontend": "workspace:*",
"@types/chrome": "^0.1.24",
"@types/webextension-polyfill": "^0.12.4",
"typescript": "~5.9.3",
"vite": "^7.1.7",
"@openbanker/frontend": "workspace:*",
"@openbanker/core": "workspace:*"
"vite": "^7.1.7"
},
"dependencies": {
"webextension-polyfill": "^0.12.0"
}
}
8 changes: 7 additions & 1 deletion packages/chrome/scripts/copy-frontend.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import { cpSync } from 'node:fs';
import { resolve } from 'node:path';

/**
* Get the build target from the command line argument (e.g., "node copy-frontend.js firefox")
* If no argument is provided, default to 'chrome'.
*/
const target = process.argv[2] || 'chrome';

const source = resolve('../frontend/dist');
const destination = resolve('./dist');
const destination = resolve(`./dist/${target}`);

try {
cpSync(source, destination, { recursive: true });
Expand Down
80 changes: 49 additions & 31 deletions packages/chrome/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,60 @@
import { defineConfig } from "vite";
import { resolve } from "path";
import fs from "fs";

// https://vite.dev/config/
export default defineConfig({
/* import aliases */
resolve: {
alias: {
"@": resolve(__dirname, "src"),
},
},
// Merges the base manifest with the platform-specific manifest.
function mergeManifests(target) {
// Read the shared configuration (icons, content scripts, permissions)
const base = JSON.parse(fs.readFileSync("./manifest.base.json", "utf-8"));

// Read platform-specific overrides (background scripts vs service workers, IDs)
const specific = JSON.parse(fs.readFileSync(`./manifest.${target}.json`, "utf-8"));

// Merge them
return { ...base, ...specific };
}

/* Use relative paths for Chrome extension */
// Remove if unecessary
// base: "./",
export default defineConfig(({ mode }) => {
// Default to 'chrome' if the target env var is not set
const target = process.env.TARGET || "chrome";

build: {
outDir: "dist",
rollupOptions: {
input: {
background: resolve(__dirname, "src/background.ts"),
bridge: resolve(__dirname, "src/bridge.ts"),
return {
resolve: {
alias: {
"@": resolve(__dirname, "src"),
},
output: {
entryFileNames: (chunkInfo) => {
// Keep chrome extension files in chrome/ folder
if (["background", "bridge"].includes(chunkInfo.name as string)) {
return "[name].js";
}
// Main app files go in assets/
return "assets/[name]-[hash].js";
},
build: {
// Separate output directories to avoid overwriting builds
// e.g., dist/chrome or dist/firefox
outDir: `dist/${target}`,
emptyOutDir: true,
rollupOptions: {
input: {
background: resolve(__dirname, "src/background.ts"),
bridge: resolve(__dirname, "src/bridge.ts"),
},
assetFileNames: () => {
// Keep assets organized
return "assets/[name]-[hash][extname]";
output: {
entryFileNames: "[name].js",
assetFileNames: "assets/[name]-[hash][extname]",
},
},
},
// Ensure relative paths work correctly in the extension
// assetsDir: "assets",
},
plugins: [
{
name: "generate-manifest",
/**
* Custom plugin to generate the manifest.json file at build time.
* This ensures the correct manifest version is bundled for the specific browser.
*/
closeBundle() {
const manifest = mergeManifests(target);
fs.writeFileSync(
`dist/${target}/manifest.json`,
JSON.stringify(manifest, null, 2)
);
},
},
],
};
});
10 changes: 7 additions & 3 deletions packages/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
"@blueprintjs/core": "^6.3.4",
"@blueprintjs/table": "^6.0.8",
"@google/genai": "^1.29.0",
"@openbanker/core": "workspace:*",
"@openbanker/plugins": "workspace:*",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-label": "^2.1.8",
"@radix-ui/react-separator": "^1.1.8",
Expand All @@ -20,6 +22,7 @@
"@types/node": "^24.7.2",
"@types/react": "^19.1.16",
"@types/react-dom": "^19.1.9",
"@types/webextension-polyfill": "^0.12.4",
"@vitejs/plugin-react": "^5.0.4",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
Expand All @@ -36,13 +39,14 @@
"tailwindcss": "^4.1.14",
"tw-animate-css": "^1.4.0",
"typescript": "~5.9.3",
"vite": "^7.1.7",
"@openbanker/plugins": "workspace:*",
"@openbanker/core": "workspace:*"
"vite": "^7.1.7"
},
"scripts": {
"dev": "cross-env PORT=8001 vite",
"build": "tsc -b && vite build",
"preview": "vite preview"
},
"dependencies": {
"webextension-polyfill": "^0.12.0"
}
}
13 changes: 7 additions & 6 deletions packages/frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import config from "@/config";
import TransactionsTable from "@/components/TransactionsTable";
import ActionButtons from "@/components/ActionButtons";
import EmptyState from "@/components/EmptyState";
import { emptyTransactionStore } from "@openbanker/core/types";
// Import Transaction type to use it for casting
import { emptyTransactionStore, type Transaction } from "@openbanker/core/types";
import { getChromeContext } from "@/lib/utils";
import useChromeStorage from "@/hooks/useChromeStorage";
import browser from "webextension-polyfill";

export default function App() {
const [transactionStore, setTransactionStore] = useChromeStorage("transactionStore", emptyTransactionStore())
Expand All @@ -21,25 +23,24 @@ export default function App() {
setTransactionStore(emptyTransactionStore())

async function detectTransactions() {
let [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
let [tab] = await browser.tabs.query({ active: true, currentWindow: true });
if (!tab.url || !tab.id) return;

const plugin = config.plugins.find(p => p.urlPattern.test(tab.url ?? "FALLBACK"));
if (plugin !== undefined) {

const res = await chrome.scripting.executeScript({
const res = await browser.scripting.executeScript({
target: { tabId: tab.id },
func: plugin.scrapeFunc,
});

if (res && res[0] && res[0].result) {
setTransactionStore({ pluginName: plugin.name, transactions: res[0].result });
const transactions = res[0].result as Transaction[];
setTransactionStore({ pluginName: plugin.name, transactions });
}

}
}
detectTransactions();

}, []);

return (
Expand Down
54 changes: 20 additions & 34 deletions packages/frontend/src/hooks/useChromeStorage.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useState, useEffect, useCallback } from "react";
import { type AppStorage } from "@openbanker/core/types";
import { getChromeContext } from "@/lib/utils";
import browser from "webextension-polyfill";

export const CHROME_STORAGE_STRATEGY: StorageArea = "local";

Expand All @@ -26,43 +27,33 @@ function useChromeStorage<K extends keyof AppStorage>(
if (getChromeContext() !== "extension") return;

const handleStorageChange = (
changes: { [key: string]: chrome.storage.StorageChange },
changes: { [key: string]: browser.Storage.StorageChange },
areaName: string
) => {
if (areaName === storageArea && key in changes) {
setStoredValue(changes[key].newValue as AppStorage[K]);
}
};

chrome.storage.onChanged.addListener(handleStorageChange);
browser.storage.onChanged.addListener(handleStorageChange);

chrome.storage[storageArea].get([key], (result) => {
if (chrome.runtime.lastError) {
console.error(
`Error getting ${String(key)} from chrome.storage.${storageArea}:`,
chrome.runtime.lastError
);
return;
}

if (result[key] !== undefined) {
setStoredValue(result[key] as AppStorage[K]);
} else {
chrome.storage[storageArea].set({ [key]: initialValue }, () => {
if (chrome.runtime.lastError) {
console.error(
`Error setting initial ${String(
key
)} in chrome.storage.${storageArea}:`,
chrome.runtime.lastError
);
}
});
}
});
browser.storage[storageArea].get([key])
.then((result) => {
if (result[key] !== undefined) {
setStoredValue(result[key] as AppStorage[K]);
} else {
// If key doesn't exist, set the initial value
browser.storage[storageArea].set({ [key]: initialValue }).catch((err) => {
console.error(`Error setting initial value for ${String(key)}:`, err);
});
}
})
.catch((error) => {
console.error(`Error getting ${String(key)} from storage:`, error);
});

return () => {
chrome.storage.onChanged.removeListener(handleStorageChange);
browser.storage.onChanged.removeListener(handleStorageChange);
};
}, [key, initialValue, storageArea]);

Expand All @@ -73,13 +64,8 @@ function useChromeStorage<K extends keyof AppStorage>(
const valueToStore =
value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
chrome.storage[storageArea].set({ [key]: valueToStore }, () => {
if (chrome.runtime.lastError) {
console.error(
`Error setting ${String(key)} in chrome.storage.${storageArea}:`,
chrome.runtime.lastError
);
}
browser.storage[storageArea].set({ [key]: valueToStore }).catch((error) => {
console.error(`Error setting ${String(key)} in storage:`, error);
});
},
[key, storageArea, storedValue]
Expand Down
14 changes: 8 additions & 6 deletions packages/frontend/src/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
// [FIX] Tell TypeScript that 'browser' is a valid global variable
declare const browser: any;

export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}

export function getChromeContext(): 'extension' | 'web_page' {
if(import.meta.env.APP_MODE === "SOLO") return 'web_page';
if (import.meta.env.APP_MODE === "SOLO") return 'web_page';

if (typeof chrome !== 'undefined' && chrome.runtime && chrome.runtime.id) {
return 'extension';
} else {
return 'web_page';
}
const isExtension =
(typeof chrome !== 'undefined' && chrome.runtime && chrome.runtime.id) ||
(typeof browser !== 'undefined' && browser.runtime && browser.runtime.id);

return isExtension ? 'extension' : 'web_page';
}