vibetunnel/web/src/hq-client.ts
Mario Zechner 8b200d6f9b refactor: major HQ architecture improvements based on review
- Remove registration retry from HQClient, let caller handle retries
- Make HQClient destroy method async and await unregister
- Remove session ID namespacing - UUIDs are unique enough
- Add /api/health endpoint for cheaper health checks
- Remove unnecessary RemoteServer.status field
- Track sessions by remote using sessionIds Set
- Fix remote session creation to use remote's token (not HQ's auth)
- Update session proxy to lookup remote by session ID
- Make cleanup-exited work across all remotes
- Remove tty_fwd_path code - always use node-pty
- Fix duplicate HQ endpoints
- Improve health check to try /api/health first, fall back to /api/sessions
- Remove offline remotes automatically on failed health check

BREAKING CHANGES:
- HQClient.destroy() is now async
- RemoteServer no longer has status field
- Session IDs are no longer namespaced with remoteId prefix

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-06-20 12:16:43 +02:00

72 lines
2.2 KiB
TypeScript

import { v4 as uuidv4 } from 'uuid';
export class HQClient {
private readonly hqUrl: string;
private readonly remoteId: string;
private readonly remoteName: string;
private readonly token: string;
private readonly hqUsername: string;
private readonly hqPassword: string;
constructor(hqUrl: string, hqUsername: string, hqPassword: string, remoteName: string) {
this.hqUrl = hqUrl;
this.remoteId = uuidv4();
this.remoteName = remoteName;
this.token = uuidv4();
this.hqUsername = hqUsername;
this.hqPassword = hqPassword;
}
async register(): Promise<void> {
try {
const response = await fetch(`${this.hqUrl}/api/remotes/register`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Basic ${Buffer.from(`${this.hqUsername}:${this.hqPassword}`).toString('base64')}`,
},
body: JSON.stringify({
id: this.remoteId,
name: this.remoteName,
url: `http://localhost:${process.env.PORT || 4020}`,
token: this.token, // Token for HQ to authenticate with this remote
}),
});
if (!response.ok) {
const errorBody = await response.json().catch(() => ({ error: response.statusText }));
throw new Error(`Registration failed: ${errorBody.error || response.statusText}`);
}
console.log(`Successfully registered with HQ at ${this.hqUrl}`);
console.log(`Remote ID: ${this.remoteId}`);
console.log(`Remote name: ${this.remoteName}`);
console.log(`Token: ${this.token}`);
} catch (error) {
console.error('Failed to register with HQ:', error);
throw error; // Let the caller handle retries if needed
}
}
async destroy(): Promise<void> {
try {
// Try to unregister
await fetch(`${this.hqUrl}/api/remotes/${this.remoteId}`, {
method: 'DELETE',
headers: {
Authorization: `Basic ${Buffer.from(`${this.hqUsername}:${this.hqPassword}`).toString('base64')}`,
},
});
} catch {
// Ignore errors during shutdown
}
}
getRemoteId(): string {
return this.remoteId;
}
getToken(): string {
return this.token;
}
}