clay-util
Version:
A beautiful, modern terminal for the web - Perfect for ChromeOS users without terminal access
175 lines (149 loc) • 4.67 kB
text/typescript
/**
* Bridge Backend - Connects to local Node.js bridge server for real system access
*/
import type { TerminalBackend, OutputCallback, ErrorCallback } from '../types';
export class BridgeBackend implements TerminalBackend {
private ws: WebSocket | null = null;
private sessionId: string | null = null;
private isConnected: boolean = false;
private onOutputCallback: ((data: string) => void) | null = null;
private onExitCallback: ((code: number, signal: number) => void) | null = null;
private onErrorCallback: ((error: string) => void) | null = null;
private bridgeUrl: string;
constructor(bridgeUrl: string = 'ws://127.0.0.1:8765/ws') {
this.bridgeUrl = bridgeUrl;
}
async connect(): Promise<void> {
return new Promise((resolve, reject) => {
try {
this.ws = new WebSocket(this.bridgeUrl);
this.ws.onopen = () => {
this.isConnected = true;
};
this.ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
switch (data.type) {
case 'connected':
this.sessionId = data.sessionId;
if (this.onOutputCallback) {
this.onOutputCallback(`\x1b[32m[Connected]\x1b[0m Bridge: ${data.shell}\r\n`);
}
resolve();
break;
case 'output':
if (this.onOutputCallback && data.sessionId === this.sessionId) {
this.onOutputCallback(data.data);
}
break;
case 'exit':
if (this.onExitCallback && data.sessionId === this.sessionId) {
this.onExitCallback(data.code || 0, data.signal || 0);
}
break;
case 'error':
if (this.onErrorCallback) {
this.onErrorCallback(data.message);
}
reject(new Error(data.message));
break;
}
} catch (error) {
console.error('Error parsing message:', error);
}
};
this.ws.onerror = (error) => {
this.isConnected = false;
if (this.onErrorCallback) {
this.onErrorCallback('Connection error');
}
reject(error);
};
this.ws.onclose = () => {
this.isConnected = false;
};
} catch (error) {
reject(error);
}
});
}
disconnect(): void {
if (this.ws) {
this.ws.close();
this.ws = null;
}
this.isConnected = false;
}
sendInput(data: string): void {
if (this.ws && this.isConnected && this.sessionId) {
this.ws.send(JSON.stringify({
type: 'input',
sessionId: this.sessionId,
data: data
}));
}
}
resize(cols: number, rows: number): void {
if (this.ws && this.isConnected && this.sessionId) {
this.ws.send(JSON.stringify({
type: 'resize',
sessionId: this.sessionId,
cols: cols,
rows: rows
}));
}
}
onOutput(callback: OutputCallback): void {
this.onOutputCallback = callback;
}
onExit(callback: (code: number, signal: number) => void): void {
this.onExitCallback = callback;
}
onError(callback: ErrorCallback): void {
this.onErrorCallback = callback;
}
getConnected(): boolean {
return this.isConnected;
}
getSessionId(): string | null {
return this.sessionId;
}
async executeCommand(command: string, cwd?: string): Promise<{ output: string; exitCode: number }> {
try {
const response = await fetch('http://127.0.0.1:8765/api/execute', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ command, cwd })
});
const data = await response.json();
return {
output: data.output || '',
exitCode: data.exitCode || (data.success ? 0 : 1)
};
} catch (error: any) {
return {
output: `Error: ${error.message}`,
exitCode: 1
};
}
}
async getSystemInfo(): Promise<any> {
try {
const response = await fetch('http://127.0.0.1:8765/api/info');
return await response.json();
} catch (error: any) {
return null;
}
}
async healthCheck(): Promise<boolean> {
try {
const response = await fetch('http://127.0.0.1:8765/api/health');
const data = await response.json();
return data.status === 'ok';
} catch (error) {
return false;
}
}
}