diff --git a/packages/web-ui/README.md b/packages/web-ui/README.md index aafe0701..6b70877d 100644 --- a/packages/web-ui/README.md +++ b/packages/web-ui/README.md @@ -205,7 +205,7 @@ setAppStorage(storage); - `LocalStorageBackend` - Uses browser localStorage - `IndexedDBBackend` - Uses IndexedDB for larger data - `SessionIndexedDBBackend` - Specialized for session storage -- `ChromeStorageBackend` - For browser extensions using chrome.storage API +- `WebExtensionStorageBackend` - For browser extensions using chrome.storage API ### Session Management @@ -293,11 +293,11 @@ await PersistentStorageDialog.request(); ### Browser Extension ```typescript -import { AppStorage, ChromeStorageBackend, Agent, ProviderTransport } from '@mariozechner/pi-web-ui'; +import { AppStorage, WebExtensionStorageBackend, Agent, ProviderTransport } from '@mariozechner/pi-web-ui'; const storage = new AppStorage({ - providerKeys: new ChromeStorageBackend(), - settings: new ChromeStorageBackend(), + providerKeys: new WebExtensionStorageBackend(), + settings: new WebExtensionStorageBackend(), }); setAppStorage(storage); ``` diff --git a/packages/web-ui/package.json b/packages/web-ui/package.json index fa8a4f40..b25b1407 100644 --- a/packages/web-ui/package.json +++ b/packages/web-ui/package.json @@ -12,7 +12,7 @@ "scripts": { "clean": "rm -rf dist", "build": "tsc -p tsconfig.build.json && tailwindcss -i ./src/app.css -o ./dist/app.css --minify", - "dev": "concurrently --names \"build,example\" --prefix-colors \"cyan,green\" \"tsc -p tsconfig.build.json --watch\" \"tailwindcss -i ./src/app.css -o ./dist/app.css --watch\" \"npm run dev --prefix example\"", + "dev": "concurrently --names \"build,example\" --prefix-colors \"cyan,green\" \"tsc -p tsconfig.build.json --watch --preserveWatchOutput\" \"tailwindcss -i ./src/app.css -o ./dist/app.css --watch\" \"npm run dev --prefix example\"", "typecheck": "tsc --noEmit && cd example && tsc --noEmit", "check": "npm run typecheck" }, diff --git a/packages/web-ui/src/index.ts b/packages/web-ui/src/index.ts index 466b17f5..f52c9724 100644 --- a/packages/web-ui/src/index.ts +++ b/packages/web-ui/src/index.ts @@ -43,10 +43,10 @@ export { SessionListDialog } from "./dialogs/SessionListDialog.js"; export { ApiKeysTab, ProxyTab, SettingsDialog, SettingsTab } from "./dialogs/SettingsDialog.js"; // Storage export { AppStorage, getAppStorage, initAppStorage, setAppStorage } from "./storage/app-storage.js"; -export { ChromeStorageBackend } from "./storage/backends/chrome-storage-backend.js"; export { IndexedDBBackend } from "./storage/backends/indexeddb-backend.js"; export { LocalStorageBackend } from "./storage/backends/local-storage-backend.js"; export { SessionIndexedDBBackend } from "./storage/backends/session-indexeddb-backend.js"; +export { WebExtensionStorageBackend } from "./storage/backends/web-extension-storage-backend.js"; export { ProviderKeysRepository } from "./storage/repositories/provider-keys-repository.js"; export { SessionRepository } from "./storage/repositories/session-repository.js"; export { SettingsRepository } from "./storage/repositories/settings-repository.js"; diff --git a/packages/web-ui/src/storage/backends/chrome-storage-backend.ts b/packages/web-ui/src/storage/backends/chrome-storage-backend.ts deleted file mode 100644 index 873afc48..00000000 --- a/packages/web-ui/src/storage/backends/chrome-storage-backend.ts +++ /dev/null @@ -1,82 +0,0 @@ -import type { StorageBackend } from "../types.js"; - -// Chrome extension API types (optional) -declare const chrome: any; - -/** - * Storage backend using chrome.storage.local. - * Good for: Browser extensions, syncing across devices (with chrome.storage.sync). - * Limits: ~10MB for local, ~100KB for sync, async API. - */ -export class ChromeStorageBackend implements StorageBackend { - constructor(private prefix: string = "") {} - - private getKey(key: string): string { - return this.prefix ? `${this.prefix}:${key}` : key; - } - - async get(key: string): Promise { - if (!chrome?.storage?.local) { - throw new Error("chrome.storage.local is not available"); - } - - const fullKey = this.getKey(key); - const result = await chrome.storage.local.get([fullKey]); - return result[fullKey] !== undefined ? (result[fullKey] as T) : null; - } - - async set(key: string, value: T): Promise { - if (!chrome?.storage?.local) { - throw new Error("chrome.storage.local is not available"); - } - - const fullKey = this.getKey(key); - await chrome.storage.local.set({ [fullKey]: value }); - } - - async delete(key: string): Promise { - if (!chrome?.storage?.local) { - throw new Error("chrome.storage.local is not available"); - } - - const fullKey = this.getKey(key); - await chrome.storage.local.remove(fullKey); - } - - async keys(): Promise { - if (!chrome?.storage?.local) { - throw new Error("chrome.storage.local is not available"); - } - - const allData = await chrome.storage.local.get(null); - const allKeys = Object.keys(allData); - const prefixWithColon = this.prefix ? `${this.prefix}:` : ""; - - if (this.prefix) { - return allKeys - .filter((key) => key.startsWith(prefixWithColon)) - .map((key) => key.substring(prefixWithColon.length)); - } - - return allKeys; - } - - async clear(): Promise { - if (!chrome?.storage?.local) { - throw new Error("chrome.storage.local is not available"); - } - - if (this.prefix) { - const keysToRemove = await this.keys(); - const fullKeys = keysToRemove.map((key) => this.getKey(key)); - await chrome.storage.local.remove(fullKeys); - } else { - await chrome.storage.local.clear(); - } - } - - async has(key: string): Promise { - const value = await this.get(key); - return value !== null; - } -} diff --git a/packages/web-ui/src/storage/backends/web-extension-storage-backend.ts b/packages/web-ui/src/storage/backends/web-extension-storage-backend.ts new file mode 100644 index 00000000..ba8c22cb --- /dev/null +++ b/packages/web-ui/src/storage/backends/web-extension-storage-backend.ts @@ -0,0 +1,83 @@ +import type { StorageBackend } from "../types.js"; + +// Cross-browser extension API compatibility +// @ts-expect-error - browser global exists in Firefox, chrome in Chrome +const browserAPI = globalThis.browser || globalThis.chrome; + +/** + * Storage backend using browser.storage.local (Firefox) or chrome.storage.local (Chrome). + * Good for: Browser extensions, syncing across devices (with storage.sync). + * Limits: ~10MB for local, ~100KB for sync, async API. + */ +export class WebExtensionStorageBackend implements StorageBackend { + constructor(private prefix: string = "") {} + + private getKey(key: string): string { + return this.prefix ? `${this.prefix}:${key}` : key; + } + + async get(key: string): Promise { + if (!browserAPI?.storage?.local) { + throw new Error("browser/chrome.storage.local is not available"); + } + + const fullKey = this.getKey(key); + const result = await browserAPI.storage.local.get([fullKey]); + return result[fullKey] !== undefined ? (result[fullKey] as T) : null; + } + + async set(key: string, value: T): Promise { + if (!browserAPI?.storage?.local) { + throw new Error("browser/chrome.storage.local is not available"); + } + + const fullKey = this.getKey(key); + await browserAPI.storage.local.set({ [fullKey]: value }); + } + + async delete(key: string): Promise { + if (!browserAPI?.storage?.local) { + throw new Error("browser/chrome.storage.local is not available"); + } + + const fullKey = this.getKey(key); + await browserAPI.storage.local.remove(fullKey); + } + + async keys(): Promise { + if (!browserAPI?.storage?.local) { + throw new Error("browser/chrome.storage.local is not available"); + } + + const allData = await browserAPI.storage.local.get(null); + const allKeys = Object.keys(allData); + const prefixWithColon = this.prefix ? `${this.prefix}:` : ""; + + if (this.prefix) { + return allKeys + .filter((key) => key.startsWith(prefixWithColon)) + .map((key) => key.substring(prefixWithColon.length)); + } + + return allKeys; + } + + async clear(): Promise { + if (!browserAPI?.storage?.local) { + throw new Error("browser/chrome.storage.local is not available"); + } + + if (this.prefix) { + const keysToRemove = await this.keys(); + const fullKeys = keysToRemove.map((key) => this.getKey(key)); + await browserAPI.storage.local.remove(fullKeys); + } else { + await browserAPI.storage.local.clear(); + } + } + + async has(key: string): Promise { + const value = await this.get(key); + return value !== null; + } +} diff --git a/pi-mono.code-workspace b/pi-mono.code-workspace index 19a651d7..f18cbab6 100644 --- a/pi-mono.code-workspace +++ b/pi-mono.code-workspace @@ -1,13 +1,16 @@ { "folders": [ { + "name": "sitegeist", + "path": "../sitegeist" + }, + { + "name": "pi-mono", "path": "." }, { + "name": "mini-lit", "path": "../mini-lit" - }, - { - "path": "../sitegeist" } ], "settings": {}