mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-15 07:04:45 +00:00
Rename ChromeStorageBackend to WebExtensionStorageBackend for cross-browser support
- Rename chrome-storage-backend.ts to web-extension-storage-backend.ts - Update to use globalThis.browser || globalThis.chrome for Firefox/Chrome compatibility - Export both names for backward compatibility - Fix tsc --preserveWatchOutput in web-ui dev script to prevent console clearing This fixes the "browser is not defined" error in Chrome extensions.
This commit is contained in:
parent
bcd109f2e9
commit
f7878c3c71
6 changed files with 95 additions and 91 deletions
|
|
@ -205,7 +205,7 @@ setAppStorage(storage);
|
||||||
- `LocalStorageBackend` - Uses browser localStorage
|
- `LocalStorageBackend` - Uses browser localStorage
|
||||||
- `IndexedDBBackend` - Uses IndexedDB for larger data
|
- `IndexedDBBackend` - Uses IndexedDB for larger data
|
||||||
- `SessionIndexedDBBackend` - Specialized for session storage
|
- `SessionIndexedDBBackend` - Specialized for session storage
|
||||||
- `ChromeStorageBackend` - For browser extensions using chrome.storage API
|
- `WebExtensionStorageBackend` - For browser extensions using chrome.storage API
|
||||||
|
|
||||||
### Session Management
|
### Session Management
|
||||||
|
|
||||||
|
|
@ -293,11 +293,11 @@ await PersistentStorageDialog.request();
|
||||||
### Browser Extension
|
### Browser Extension
|
||||||
|
|
||||||
```typescript
|
```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({
|
const storage = new AppStorage({
|
||||||
providerKeys: new ChromeStorageBackend(),
|
providerKeys: new WebExtensionStorageBackend(),
|
||||||
settings: new ChromeStorageBackend(),
|
settings: new WebExtensionStorageBackend(),
|
||||||
});
|
});
|
||||||
setAppStorage(storage);
|
setAppStorage(storage);
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"clean": "rm -rf dist",
|
"clean": "rm -rf dist",
|
||||||
"build": "tsc -p tsconfig.build.json && tailwindcss -i ./src/app.css -o ./dist/app.css --minify",
|
"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",
|
"typecheck": "tsc --noEmit && cd example && tsc --noEmit",
|
||||||
"check": "npm run typecheck"
|
"check": "npm run typecheck"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -43,10 +43,10 @@ export { SessionListDialog } from "./dialogs/SessionListDialog.js";
|
||||||
export { ApiKeysTab, ProxyTab, SettingsDialog, SettingsTab } from "./dialogs/SettingsDialog.js";
|
export { ApiKeysTab, ProxyTab, SettingsDialog, SettingsTab } from "./dialogs/SettingsDialog.js";
|
||||||
// Storage
|
// Storage
|
||||||
export { AppStorage, getAppStorage, initAppStorage, setAppStorage } from "./storage/app-storage.js";
|
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 { IndexedDBBackend } from "./storage/backends/indexeddb-backend.js";
|
||||||
export { LocalStorageBackend } from "./storage/backends/local-storage-backend.js";
|
export { LocalStorageBackend } from "./storage/backends/local-storage-backend.js";
|
||||||
export { SessionIndexedDBBackend } from "./storage/backends/session-indexeddb-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 { ProviderKeysRepository } from "./storage/repositories/provider-keys-repository.js";
|
||||||
export { SessionRepository } from "./storage/repositories/session-repository.js";
|
export { SessionRepository } from "./storage/repositories/session-repository.js";
|
||||||
export { SettingsRepository } from "./storage/repositories/settings-repository.js";
|
export { SettingsRepository } from "./storage/repositories/settings-repository.js";
|
||||||
|
|
|
||||||
|
|
@ -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<T = unknown>(key: string): Promise<T | null> {
|
|
||||||
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<T = unknown>(key: string, value: T): Promise<void> {
|
|
||||||
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<void> {
|
|
||||||
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<string[]> {
|
|
||||||
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<void> {
|
|
||||||
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<boolean> {
|
|
||||||
const value = await this.get(key);
|
|
||||||
return value !== null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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<T = unknown>(key: string): Promise<T | null> {
|
||||||
|
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<T = unknown>(key: string, value: T): Promise<void> {
|
||||||
|
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<void> {
|
||||||
|
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<string[]> {
|
||||||
|
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<void> {
|
||||||
|
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<boolean> {
|
||||||
|
const value = await this.get(key);
|
||||||
|
return value !== null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,13 +1,16 @@
|
||||||
{
|
{
|
||||||
"folders": [
|
"folders": [
|
||||||
{
|
{
|
||||||
|
"name": "sitegeist",
|
||||||
|
"path": "../sitegeist"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pi-mono",
|
||||||
"path": "."
|
"path": "."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"name": "mini-lit",
|
||||||
"path": "../mini-lit"
|
"path": "../mini-lit"
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "../sitegeist"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"settings": {}
|
"settings": {}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue