@statezero/statezero-tunnel
Version:
Tunnelmole client for StateZero users to create secure tunnels to their local services.
107 lines (93 loc) • 3.3 kB
text/typescript
import fs from 'fs';
import path from 'path';
import os from 'os';
import crypto from 'crypto';
export interface ProjectConfig {
id: string;
name: string;
subdomain: string;
userEmail: string;
userName: string;
createdAt: string;
lastUsed: string;
}
interface ProjectStore {
projects: ProjectConfig[];
activeProject?: string;
}
export class ProjectManager {
private configPath: string;
private store: ProjectStore;
constructor() {
this.configPath = path.join(os.homedir(), '.tunnelmole-projects.json');
this.store = this.loadStore();
}
private loadStore(): ProjectStore {
try {
if (fs.existsSync(this.configPath)) {
const data = fs.readFileSync(this.configPath, 'utf8');
return JSON.parse(data);
}
} catch (error) {
console.warn('Failed to load project store:', error);
}
return { projects: [] };
}
private saveStore(): void {
try {
fs.writeFileSync(this.configPath, JSON.stringify(this.store, null, 2));
} catch (error) {
console.error('Failed to save project store:', error);
}
}
public getProjects(): ProjectConfig[] {
return this.store.projects.sort((a, b) =>
new Date(b.lastUsed).getTime() - new Date(a.lastUsed).getTime()
);
}
public generateUniqueId(): string {
return crypto.randomBytes(8).toString('hex');
}
public createProject(name: string, userEmail: string, userName: string): ProjectConfig {
const project: ProjectConfig = {
id: this.generateUniqueId(),
name,
subdomain: this.generateSubdomain(name),
userEmail,
userName,
createdAt: new Date().toISOString(),
lastUsed: new Date().toISOString()
};
this.store.projects.push(project);
this.saveStore();
return project;
}
private generateSubdomain(projectName: string): string {
// Create a consistent subdomain based on project name + unique suffix
const sanitized = projectName.toLowerCase().replace(/[^a-z0-9]/g, '');
const truncated = sanitized.substring(0, 10); // Limit length
const suffix = crypto.createHash('md5').update(projectName).digest('hex').substring(0, 6);
return `${truncated}-${suffix}`;
}
public updateLastUsed(projectId: string): void {
const project = this.store.projects.find(p => p.id === projectId);
if (project) {
project.lastUsed = new Date().toISOString();
this.saveStore();
}
}
public deleteProject(projectId: string): boolean {
const index = this.store.projects.findIndex(p => p.id === projectId);
if (index !== -1) {
this.store.projects.splice(index, 1);
this.saveStore();
return true;
}
return false;
}
public findByName(name: string): ProjectConfig | undefined {
return this.store.projects.find(p =>
p.name.toLowerCase() === name.toLowerCase()
);
}
}