/** * Workspace Manager Module * Handles request collections, workspaces, environments, and persistence */ const WorkspaceManager = (() => { "use strict"; const STORAGE_KEY = 'DoxaApi.workspaces'; const CURRENT_WS_KEY = 'DoxaApi.currentWorkspace'; const ENV_KEY = 'DoxaApi.environments'; /** * Workspace structure */ class Workspace { constructor(name = 'Default') { this.id = generateId(); this.name = name; this.description = ''; this.collections = []; this.createdAt = Date.now(); this.updatedAt = Date.now(); } } /** * Collection structure (folders for organizing requests) */ class Collection { constructor(name = 'New Collection') { this.id = generateId(); this.name = name; this.description = ''; this.requests = []; this.folders = []; this.createdAt = Date.now(); } } /** * Request in a collection */ class SavedRequest { constructor(name = 'New Request', method = 'GET', path = '') { this.id = generateId(); this.name = name; this.method = method; this.path = path; this.description = ''; this.tags = []; this.params = { path: {}, query: {}, header: {}, cookie: {} }; this.body = null; this.bodyType = 'json'; this.formData = {}; this.auth = null; this.createdAt = Date.now(); this.updatedAt = Date.now(); } clone() { const copy = new SavedRequest(this.name, this.method, this.path); copy.id = generateId(); copy.description = this.description; copy.tags = [...this.tags]; copy.params = JSON.parse(JSON.stringify(this.params)); copy.body = this.body; copy.bodyType = this.bodyType; copy.formData = { ...this.formData }; copy.auth = this.auth; return copy; } } /** * Environment for variable substitution */ class Environment { constructor(name = 'Default') { this.id = generateId(); this.name = name; this.description = ''; this.variables = {}; this.isActive = false; this.createdAt = Date.now(); } } /** * Generate unique ID */ function generateId() { return 'id_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); } /** * Get all workspaces */ function getWorkspaces() { try { const data = localStorage.getItem(STORAGE_KEY); return data ? JSON.parse(data).map(deserializeWorkspace) : []; } catch (e) { console.error('Failed to load workspaces:', e); return []; } } /** * Get current active workspace */ function getCurrentWorkspace() { const workspaces = getWorkspaces(); if (workspaces.length === 0) { const ws = new Workspace('Default'); saveWorkspace(ws); return ws; } return workspaces[0]; } /** * Save workspace */ function saveWorkspace(workspace) { workspace.updatedAt = Date.now(); const workspaces = getWorkspaces(); const index = workspaces.findIndex(w => w.id === workspace.id); if (index >= 0) { workspaces[index] = workspace; } else { workspaces.push(workspace); } localStorage.setItem(STORAGE_KEY, JSON.stringify(workspaces)); return workspace; } /** * Delete workspace */ function deleteWorkspace(workspaceId) { let workspaces = getWorkspaces(); workspaces = workspaces.filter(w => w.id !== workspaceId); localStorage.setItem(STORAGE_KEY, JSON.stringify(workspaces)); } /** * Add collection to workspace */ function addCollection(workspaceId, collection) { const workspace = getWorkspaces().find(w => w.id === workspaceId); if (workspace) { workspace.collections.push(collection); saveWorkspace(workspace); } return collection; } /** * Save request to collection */ function saveRequestToCollection(workspaceId, collectionId, request) { const workspace = getWorkspaces().find(w => w.id === workspaceId); if (!workspace) return null; const collection = findCollectionRecursive(workspace.collections, collectionId); if (!collection) return null; request.updatedAt = Date.now(); const index = collection.requests.findIndex(r => r.id === request.id); if (index >= 0) { collection.requests[index] = request; } else { collection.requests.push(request); } saveWorkspace(workspace); return request; } /** * Find collection recursively in folder structure */ function findCollectionRecursive(collections, collectionId) { for (const col of collections) { if (col.id === collectionId) return col; if (col.folders) { const found = findCollectionRecursive(col.folders, collectionId); if (found) return found; } } return null; } /** * Get all requests in workspace */ function getAllRequests(workspaceId) { const workspace = getWorkspaces().find(w => w.id === workspaceId); if (!workspace) return []; const requests = []; function traverse(collections) { collections.forEach(col => { requests.push(...(col.requests || [])); if (col.folders) { traverse(col.folders); } }); } traverse(workspace.collections); return requests; } /** * Search requests in workspace */ function searchRequests(workspaceId, query) { const allRequests = getAllRequests(workspaceId); const lowerQuery = query.toLowerCase(); return allRequests.filter(req => req.name.toLowerCase().includes(lowerQuery) || req.path.toLowerCase().includes(lowerQuery) || req.description.toLowerCase().includes(lowerQuery) || (req.tags && req.tags.some(t => t.toLowerCase().includes(lowerQuery))) ); } /** * Get environments */ function getEnvironments() { try { const data = localStorage.getItem(ENV_KEY); return data ? JSON.parse(data).map(deserializeEnvironment) : []; } catch (e) { console.error('Failed to load environments:', e); return []; } } /** * Save environment */ function saveEnvironment(environment) { const environments = getEnvironments(); const index = environments.findIndex(e => e.id === environment.id); if (index >= 0) { environments[index] = environment; } else { environments.push(environment); } localStorage.setItem(ENV_KEY, JSON.stringify(environments)); return environment; } /** * Delete environment */ function deleteEnvironment(environmentId) { let environments = getEnvironments(); environments = environments.filter(e => e.id !== environmentId); localStorage.setItem(ENV_KEY, JSON.stringify(environments)); } /** * Get active environment */ function getActiveEnvironment() { const environments = getEnvironments(); return environments.find(e => e.isActive) || null; } /** * Set active environment */ function setActiveEnvironment(environmentId) { const environments = getEnvironments(); environments.forEach(e => e.isActive = e.id === environmentId); localStorage.setItem(ENV_KEY, JSON.stringify(environments)); } /** * Substitute variables in string */ function substituteVariables(str, environment) { if (!str || !environment) return str; let result = str; Object.entries(environment.variables).forEach(([name, value]) => { result = result.replace(new RegExp(`\\$\\{${name}\\}|\\$${name}\\b`, 'g'), String(value || '')); }); return result; } /** * Export workspace as JSON */ function exportWorkspace(workspaceId) { const workspace = getWorkspaces().find(w => w.id === workspaceId); if (!workspace) return null; return { version: '1.0', exported: new Date().toISOString(), workspace: workspace, environments: getEnvironments() }; } /** * Import workspace from JSON */ function importWorkspace(json) { try { const data = typeof json === 'string' ? JSON.parse(json) : json; // Validate version if (!data.version) throw new Error('Invalid format: missing version'); // Create new workspace with imported data const workspace = data.workspace; workspace.id = generateId(); workspace.createdAt = Date.now(); workspace.updatedAt = Date.now(); // Import environments if (data.environments && Array.isArray(data.environments)) { data.environments.forEach(env => { env.id = generateId(); saveEnvironment(env); }); } // Save workspace return saveWorkspace(workspace); } catch (e) { console.error('Failed to import workspace:', e); return null; } } /** * Duplicate request */ function duplicateRequest(request) { const duplicate = request.clone(); duplicate.name = `${request.name} (copy)`; return duplicate; } /** * Duplicate collection */ function duplicateCollection(collection) { const duplicate = new Collection(collection.name + ' (copy)'); duplicate.description = collection.description; duplicate.requests = collection.requests.map(r => r.clone ? r.clone() : r); if (collection.folders) { duplicate.folders = collection.folders.map(f => duplicateCollection(f)); } return duplicate; } /** * Get recent requests */ function getRecentRequests(workspaceId, limit = 10) { return getAllRequests(workspaceId) .sort((a, b) => (b.updatedAt || 0) - (a.updatedAt || 0)) .slice(0, limit); } /** * Deserialize workspace (helper for JSON parsing) */ function deserializeWorkspace(data) { const ws = Object.assign(new Workspace(data.name), data); return ws; } /** * Deserialize environment (helper for JSON parsing) */ function deserializeEnvironment(data) { const env = Object.assign(new Environment(data.name), data); return env; } // Public API return { Workspace, Collection, SavedRequest, Environment, generateId, getWorkspaces, getCurrentWorkspace, saveWorkspace, deleteWorkspace, addCollection, saveRequestToCollection, getAllRequests, searchRequests, getEnvironments, saveEnvironment, deleteEnvironment, getActiveEnvironment, setActiveEnvironment, substituteVariables, exportWorkspace, importWorkspace, duplicateRequest, duplicateCollection, getRecentRequests }; })(); // Export for use in other modules if (typeof module !== 'undefined' && module.exports) { module.exports = WorkspaceManager; }