UNPKG

@semantest/chrome-extension

Version:

Browser extension for ChatGPT-buddy - AI automation extension built on Web-Buddy framework

300 lines (295 loc) 15.4 kB
/* Web-Buddy Framework Chrome Downloads Infrastructure Adapter Copyright (C) 2025-today rydnr@acm-sl.org This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. */ var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) { var useValue = arguments.length > 2; for (var i = 0; i < initializers.length; i++) { value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg); } return useValue ? value : void 0; }; var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) { function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; } var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value"; var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null; var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {}); var _, done = false; for (var i = decorators.length - 1; i >= 0; i--) { var context = {}; for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p]; for (var p in contextIn.access) context.access[p] = contextIn.access[p]; context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); }; var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context); if (kind === "accessor") { if (result === void 0) continue; if (result === null || typeof result !== "object") throw new TypeError("Object expected"); if (_ = accept(result.get)) descriptor.get = _; if (_ = accept(result.set)) descriptor.set = _; if (_ = accept(result.init)) initializers.unshift(_); } else if (_ = accept(result)) { if (kind === "field") initializers.unshift(_); else descriptor[key] = _; } } if (target) Object.defineProperty(target, contextIn.name, descriptor); done = true; }; import { Adapter, listen } from '@typescript-eda/core'; import { FileDownloadProgress, FileDownloadCompleted, FileDownloadFailed, ServerFileAccessRequested, ServerFileAccessProvided } from '../../domain/events/download-events'; /** * Chrome Downloads Adapter - Bridges Chrome Downloads API with domain events * * Responsibilities: * - Listen to Chrome downloads API events * - Convert Chrome download states to domain events * - Provide file system access for downloaded files * - Handle download interruptions and errors */ let ChromeDownloadsAdapter = (() => { var _a; let _classSuper = Adapter; let _instanceExtraInitializers = []; let _handleServerFileAccess_decorators; return _a = class ChromeDownloadsAdapter extends _classSuper { constructor() { super(); this.activeDownloads = (__runInitializers(this, _instanceExtraInitializers), new Map()); this.downloadListeners = new Set(); this.initializeDownloadListeners(); } /** * Initializes Chrome Downloads API listeners */ initializeDownloadListeners() { // Listen for download state changes chrome.downloads.onChanged.addListener((downloadDelta) => { this.handleDownloadChange(downloadDelta); }); // Listen for download creation chrome.downloads.onCreated.addListener((downloadItem) => { this.activeDownloads.set(downloadItem.id, downloadItem); this.emitProgressEvent(downloadItem); }); // Listen for download determination (file path resolved) chrome.downloads.onDeterminingFilename.addListener((downloadItem, suggest) => { this.activeDownloads.set(downloadItem.id, downloadItem); // Let Chrome determine the filename automatically suggest(); }); } /** * Handles download state changes from Chrome API */ handleDownloadChange(downloadDelta) { const downloadId = downloadDelta.id; const currentDownload = this.activeDownloads.get(downloadId); if (!currentDownload) { // Fetch download info if not cached chrome.downloads.search({ id: downloadId }, (downloads) => { if (downloads.length > 0) { const download = downloads[0]; this.activeDownloads.set(downloadId, download); this.emitProgressEvent(download); } }); return; } // Update cached download with changes const updatedDownload = this.applyDownloadDelta(currentDownload, downloadDelta); this.activeDownloads.set(downloadId, updatedDownload); this.emitProgressEvent(updatedDownload); // Handle completion or failure if (downloadDelta.state?.current === 'complete') { this.handleDownloadCompleted(updatedDownload); } else if (downloadDelta.state?.current === 'interrupted') { this.handleDownloadFailed(updatedDownload, downloadDelta.error?.current); } } /** * Applies download delta changes to cached download item */ applyDownloadDelta(download, delta) { return { ...download, state: delta.state?.current || download.state, paused: delta.paused?.current ?? download.paused, error: delta.error?.current || download.error, bytesReceived: delta.bytesReceived?.current ?? download.bytesReceived, totalBytes: delta.totalBytes?.current ?? download.totalBytes, filename: delta.filename?.current || download.filename, exists: delta.exists?.current ?? download.exists }; } /** * Emits progress event for download state changes */ emitProgressEvent(download) { const progressEvent = new FileDownloadProgress(download.id, download.url, download.filename, download.state, download.bytesReceived, download.totalBytes, download.filename, // filepath is same as filename in Chrome downloads download.error); // Emit to all registered listeners this.downloadListeners.forEach(listener => { try { listener(progressEvent); } catch (error) { console.error('Error in download progress listener:', error); } }); } /** * Handles successful download completion */ handleDownloadCompleted(download) { const completedEvent = new FileDownloadCompleted(download.id, download.url, download.filename, download.filename, // Chrome provides relative path download.totalBytes, download.mime); this.emit(completedEvent); // Clean up from active downloads after a delay (keep for status queries) setTimeout(() => { this.activeDownloads.delete(download.id); }, 60000); // Keep for 1 minute } /** * Handles download failures */ handleDownloadFailed(download, error) { const failedEvent = new FileDownloadFailed('', // No correlation ID for Chrome-initiated events download.url, error || download.error || 'Download interrupted', download.id); this.emit(failedEvent); // Clean up from active downloads setTimeout(() => { this.activeDownloads.delete(download.id); }, 30000); // Keep for 30 seconds for error analysis } /** * Registers a progress listener */ addProgressListener(listener) { this.downloadListeners.add(listener); } /** * Removes a progress listener */ removeProgressListener(listener) { this.downloadListeners.delete(listener); } /** * Handles server file access requests */ async handleServerFileAccess(event) { try { const download = this.activeDownloads.get(event.downloadId); if (!download || download.state !== 'complete') { const errorEvent = new ServerFileAccessProvided(event.correlationId, event.downloadId, event.requestedOperation, false, undefined, undefined, 'Download not found or not completed'); this.emit(errorEvent); return; } switch (event.requestedOperation) { case 'read': await this.handleFileRead(event, download); break; case 'copy': await this.handleFileCopy(event, download); break; case 'move': await this.handleFileMove(event, download); break; case 'delete': await this.handleFileDelete(event, download); break; default: throw new Error(`Unsupported operation: ${event.requestedOperation}`); } } catch (error) { const errorEvent = new ServerFileAccessProvided(event.correlationId, event.downloadId, event.requestedOperation, false, undefined, undefined, error instanceof Error ? error.message : 'File operation failed'); this.emit(errorEvent); } } /** * Handles file read operations (limited in browser context) */ async handleFileRead(event, download) { // Note: Direct file reading is limited in browser extension context // This would typically require native messaging or file system API const responseEvent = new ServerFileAccessProvided(event.correlationId, event.downloadId, 'read', true, download.filename, undefined, // Content reading requires additional permissions undefined); this.emit(responseEvent); } /** * Handles file copy operations */ async handleFileCopy(event, download) { // File copy operations require native messaging or additional APIs // For now, we provide the file path information const responseEvent = new ServerFileAccessProvided(event.correlationId, event.downloadId, 'copy', true, download.filename, undefined, undefined); this.emit(responseEvent); } /** * Handles file move operations */ async handleFileMove(event, download) { // File move operations are limited in browser context const responseEvent = new ServerFileAccessProvided(event.correlationId, event.downloadId, 'move', false, undefined, undefined, 'File move not supported in browser context'); this.emit(responseEvent); } /** * Handles file delete operations */ async handleFileDelete(event, download) { try { // Use Chrome Downloads API to remove download await new Promise((resolve, reject) => { chrome.downloads.removeFile(download.id, () => { if (chrome.runtime.lastError) { reject(new Error(chrome.runtime.lastError.message)); } else { resolve(); } }); }); const responseEvent = new ServerFileAccessProvided(event.correlationId, event.downloadId, 'delete', true, download.filename, undefined, undefined); this.emit(responseEvent); } catch (error) { const errorEvent = new ServerFileAccessProvided(event.correlationId, event.downloadId, 'delete', false, undefined, undefined, error instanceof Error ? error.message : 'File deletion failed'); this.emit(errorEvent); } } /** * Gets current downloads status */ getActiveDownloads() { return new Map(this.activeDownloads); } /** * Cleanup method */ dispose() { this.downloadListeners.clear(); this.activeDownloads.clear(); } }, (() => { const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0; _handleServerFileAccess_decorators = [listen(ServerFileAccessRequested)]; __esDecorate(_a, null, _handleServerFileAccess_decorators, { kind: "method", name: "handleServerFileAccess", static: false, private: false, access: { has: obj => "handleServerFileAccess" in obj, get: obj => obj.handleServerFileAccess }, metadata: _metadata }, null, _instanceExtraInitializers); if (_metadata) Object.defineProperty(_a, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata }); })(), _a; })(); export { ChromeDownloadsAdapter };