@semantest/chrome-extension
Version:
Browser extension for ChatGPT-buddy - AI automation extension built on Web-Buddy framework
333 lines (332 loc) • 14.9 kB
JavaScript
// IndexedDB Storage Manager for Web-Buddy
// Provides persistent storage for learned automation patterns and user interactions
class WebBuddyStorage {
constructor() {
this.db = null;
this.DB_NAME = 'WebBuddyDB';
this.DB_VERSION = 1;
this.init();
}
async init() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.DB_NAME, this.DB_VERSION);
request.onerror = () => {
console.error('❌ Failed to open IndexedDB:', request.error);
reject(request.error);
};
request.onsuccess = () => {
this.db = request.result;
console.log('✅ IndexedDB initialized successfully');
resolve();
};
request.onupgradeneeded = (event) => {
const db = event.target.result;
// Create automation patterns store
if (!db.objectStoreNames.contains('automationPatterns')) {
const patternsStore = db.createObjectStore('automationPatterns', { keyPath: 'id' });
patternsStore.createIndex('domain', 'domain', { unique: false });
patternsStore.createIndex('action', 'action', { unique: false });
patternsStore.createIndex('url', 'url', { unique: false });
patternsStore.createIndex('timestamp', 'timestamp', { unique: false });
patternsStore.createIndex('success', 'success', { unique: false });
}
// Create user interactions store
if (!db.objectStoreNames.contains('userInteractions')) {
const interactionsStore = db.createObjectStore('userInteractions', { keyPath: 'id' });
interactionsStore.createIndex('sessionId', 'sessionId', { unique: false });
interactionsStore.createIndex('domain', 'domain', { unique: false });
interactionsStore.createIndex('eventType', 'eventType', { unique: false });
interactionsStore.createIndex('timestamp', 'timestamp', { unique: false });
}
// Create website configurations store
if (!db.objectStoreNames.contains('websiteConfigs')) {
const configsStore = db.createObjectStore('websiteConfigs', { keyPath: 'domain' });
configsStore.createIndex('lastAccessed', 'lastAccessed', { unique: false });
configsStore.createIndex('automationEnabled', 'automationEnabled', { unique: false });
}
console.log('🔧 IndexedDB schema updated to version', this.DB_VERSION);
};
});
}
// Automation Patterns Methods
async saveAutomationPattern(pattern) {
if (!this.db)
throw new Error('Database not initialized');
const fullPattern = {
...pattern,
id: `pattern_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
timestamp: Date.now()
};
return new Promise((resolve, reject) => {
const transaction = this.db.transaction(['automationPatterns'], 'readwrite');
const store = transaction.objectStore('automationPatterns');
const request = store.add(fullPattern);
request.onsuccess = () => {
console.log('✅ Automation pattern saved:', fullPattern.id);
resolve(fullPattern.id);
};
request.onerror = () => {
console.error('❌ Failed to save automation pattern:', request.error);
reject(request.error);
};
});
}
async getAutomationPatterns(filters) {
if (!this.db)
throw new Error('Database not initialized');
return new Promise((resolve, reject) => {
const transaction = this.db.transaction(['automationPatterns'], 'readonly');
const store = transaction.objectStore('automationPatterns');
let request;
if (filters?.domain) {
const index = store.index('domain');
request = index.getAll(filters.domain);
}
else if (filters?.action) {
const index = store.index('action');
request = index.getAll(filters.action);
}
else {
request = store.getAll();
}
request.onsuccess = () => {
let patterns = request.result;
// Apply additional filters
if (filters) {
if (filters.url) {
patterns = patterns.filter(p => p.url.includes(filters.url));
}
if (filters.successOnly) {
patterns = patterns.filter(p => p.success);
}
if (filters.limit) {
patterns = patterns.slice(0, filters.limit);
}
}
// Sort by timestamp (newest first)
patterns.sort((a, b) => b.timestamp - a.timestamp);
resolve(patterns);
};
request.onerror = () => {
console.error('❌ Failed to get automation patterns:', request.error);
reject(request.error);
};
});
}
async updateAutomationPattern(id, updates) {
if (!this.db)
throw new Error('Database not initialized');
return new Promise(async (resolve, reject) => {
const transaction = this.db.transaction(['automationPatterns'], 'readwrite');
const store = transaction.objectStore('automationPatterns');
// Get existing pattern
const getRequest = store.get(id);
getRequest.onsuccess = () => {
const existingPattern = getRequest.result;
if (!existingPattern) {
reject(new Error('Pattern not found'));
return;
}
// Merge updates
const updatedPattern = { ...existingPattern, ...updates };
// Save updated pattern
const putRequest = store.put(updatedPattern);
putRequest.onsuccess = () => {
console.log('✅ Automation pattern updated:', id);
resolve();
};
putRequest.onerror = () => {
console.error('❌ Failed to update automation pattern:', putRequest.error);
reject(putRequest.error);
};
};
getRequest.onerror = () => {
console.error('❌ Failed to get automation pattern for update:', getRequest.error);
reject(getRequest.error);
};
});
}
// User Interactions Methods
async saveUserInteraction(interaction) {
if (!this.db)
throw new Error('Database not initialized');
const fullInteraction = {
...interaction,
id: `interaction_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
timestamp: Date.now()
};
return new Promise((resolve, reject) => {
const transaction = this.db.transaction(['userInteractions'], 'readwrite');
const store = transaction.objectStore('userInteractions');
const request = store.add(fullInteraction);
request.onsuccess = () => {
console.log('✅ User interaction saved:', fullInteraction.id);
resolve(fullInteraction.id);
};
request.onerror = () => {
console.error('❌ Failed to save user interaction:', request.error);
reject(request.error);
};
});
}
async getUserInteractions(filters) {
if (!this.db)
throw new Error('Database not initialized');
return new Promise((resolve, reject) => {
const transaction = this.db.transaction(['userInteractions'], 'readonly');
const store = transaction.objectStore('userInteractions');
let request;
if (filters?.sessionId) {
const index = store.index('sessionId');
request = index.getAll(filters.sessionId);
}
else if (filters?.domain) {
const index = store.index('domain');
request = index.getAll(filters.domain);
}
else {
request = store.getAll();
}
request.onsuccess = () => {
let interactions = request.result;
// Apply additional filters
if (filters) {
if (filters.eventType) {
interactions = interactions.filter(i => i.eventType === filters.eventType);
}
if (filters.limit) {
interactions = interactions.slice(0, filters.limit);
}
}
// Sort by timestamp (newest first)
interactions.sort((a, b) => b.timestamp - a.timestamp);
resolve(interactions);
};
request.onerror = () => {
console.error('❌ Failed to get user interactions:', request.error);
reject(request.error);
};
});
}
// Website Configuration Methods
async saveWebsiteConfig(config) {
if (!this.db)
throw new Error('Database not initialized');
const configWithTimestamp = {
...config,
lastAccessed: Date.now()
};
return new Promise((resolve, reject) => {
const transaction = this.db.transaction(['websiteConfigs'], 'readwrite');
const store = transaction.objectStore('websiteConfigs');
const request = store.put(configWithTimestamp);
request.onsuccess = () => {
console.log('✅ Website config saved:', config.domain);
resolve();
};
request.onerror = () => {
console.error('❌ Failed to save website config:', request.error);
reject(request.error);
};
});
}
async getWebsiteConfig(domain) {
if (!this.db)
throw new Error('Database not initialized');
return new Promise((resolve, reject) => {
const transaction = this.db.transaction(['websiteConfigs'], 'readonly');
const store = transaction.objectStore('websiteConfigs');
const request = store.get(domain);
request.onsuccess = () => {
resolve(request.result || null);
};
request.onerror = () => {
console.error('❌ Failed to get website config:', request.error);
reject(request.error);
};
});
}
// Utility Methods
async clearOldData(olderThanDays = 30) {
if (!this.db)
throw new Error('Database not initialized');
const cutoffTime = Date.now() - (olderThanDays * 24 * 60 * 60 * 1000);
return new Promise((resolve, reject) => {
const transaction = this.db.transaction(['automationPatterns', 'userInteractions'], 'readwrite');
// Clear old automation patterns
const patternsStore = transaction.objectStore('automationPatterns');
const patternsIndex = patternsStore.index('timestamp');
const patternsRange = IDBKeyRange.upperBound(cutoffTime);
patternsIndex.openCursor(patternsRange).onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
cursor.delete();
cursor.continue();
}
};
// Clear old user interactions
const interactionsStore = transaction.objectStore('userInteractions');
const interactionsIndex = interactionsStore.index('timestamp');
const interactionsRange = IDBKeyRange.upperBound(cutoffTime);
interactionsIndex.openCursor(interactionsRange).onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
cursor.delete();
cursor.continue();
}
};
transaction.oncomplete = () => {
console.log(`✅ Cleared data older than ${olderThanDays} days`);
resolve();
};
transaction.onerror = () => {
console.error('❌ Failed to clear old data:', transaction.error);
reject(transaction.error);
};
});
}
async getStorageStats() {
if (!this.db)
throw new Error('Database not initialized');
return new Promise((resolve, reject) => {
const transaction = this.db.transaction(['automationPatterns', 'userInteractions', 'websiteConfigs'], 'readonly');
const stats = {
automationPatterns: 0,
userInteractions: 0,
websiteConfigs: 0
};
let completedStores = 0;
const totalStores = 3;
const checkCompletion = () => {
completedStores++;
if (completedStores === totalStores) {
resolve(stats);
}
};
// Count automation patterns
const patternsRequest = transaction.objectStore('automationPatterns').count();
patternsRequest.onsuccess = () => {
stats.automationPatterns = patternsRequest.result;
checkCompletion();
};
// Count user interactions
const interactionsRequest = transaction.objectStore('userInteractions').count();
interactionsRequest.onsuccess = () => {
stats.userInteractions = interactionsRequest.result;
checkCompletion();
};
// Count website configs
const configsRequest = transaction.objectStore('websiteConfigs').count();
configsRequest.onsuccess = () => {
stats.websiteConfigs = configsRequest.result;
checkCompletion();
};
transaction.onerror = () => {
console.error('❌ Failed to get storage stats:', transaction.error);
reject(transaction.error);
};
});
}
}
// Export singleton instance
export const webBuddyStorage = new WebBuddyStorage();