Files
gravity_web/server/bridge-client.js

183 lines
4.6 KiB
JavaScript

/**
* Bridge Client — Antigravity Extension Bridge (localhost:9850)와 통신
*
* Extension이 설치된 Antigravity에서 제공하는 REST API + WebSocket을 통해
* 대화 목록, 메시지 전송, 승인/거절 등을 수행합니다.
*/
const http = require('http');
const WebSocket = require('ws');
const BRIDGE_HOST = 'localhost';
const BRIDGE_PORT = 9850;
const BRIDGE_BASE = `http://${BRIDGE_HOST}:${BRIDGE_PORT}`;
class BridgeClient {
constructor() {
this.ws = null;
this.connected = false;
this.listeners = new Map();
this.reconnectTimer = null;
}
/**
* Bridge 서버 상태 확인
*/
async checkHealth() {
try {
const data = await this._get('/api/health');
return { available: true, ...data };
} catch {
return { available: false };
}
}
/**
* 대화 목록 (제목, stepCount, 수정일시)
*/
async getSessions() {
return this._get('/api/sessions');
}
/**
* 전체 Cascade 데이터 (대화 내용, 스텝, notify_user 등)
*/
async getCascades() {
return this._get('/api/cascades');
}
/**
* 메시지 전송
*/
async sendMessage(message, sessionId) {
return this._post('/api/send', { message, sessionId });
}
/**
* 코드 편집 승인
*/
async acceptStep() {
return this._post('/api/accept', {});
}
/**
* 코드 편집 거절
*/
async rejectStep() {
return this._post('/api/reject', {});
}
/**
* 터미널 명령 승인
*/
async acceptTerminal() {
return this._post('/api/accept-terminal', {});
}
/**
* 에이전트 설정 조회
*/
async getPreferences() {
return this._get('/api/preferences');
}
/**
* WebSocket 실시간 이벤트 연결
*/
connectWs(onMessage) {
if (this.ws) {
this.ws.close();
}
try {
this.ws = new WebSocket(`ws://${BRIDGE_HOST}:${BRIDGE_PORT}/ws`);
} catch (err) {
console.log(`[Bridge WS] 연결 실패: ${err.message}`);
this._scheduleReconnect(onMessage);
return;
}
this.ws.on('open', () => {
this.connected = true;
console.log('[Bridge WS] 연결됨');
});
this.ws.on('message', (data) => {
try {
const msg = JSON.parse(data.toString());
onMessage(msg);
} catch { }
});
this.ws.on('close', () => {
this.connected = false;
console.log('[Bridge WS] 연결 해제');
this._scheduleReconnect(onMessage);
});
this.ws.on('error', () => {
this.connected = false;
});
}
_scheduleReconnect(onMessage) {
if (this.reconnectTimer) return;
this.reconnectTimer = setTimeout(() => {
this.reconnectTimer = null;
console.log('[Bridge WS] 재연결 시도...');
this.connectWs(onMessage);
}, 5000);
}
disconnect() {
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer);
this.reconnectTimer = null;
}
if (this.ws) {
this.ws.close();
this.ws = null;
}
}
// --- HTTP 헬퍼 ---
_get(path) {
return new Promise((resolve, reject) => {
http.get(`${BRIDGE_BASE}${path}`, (res) => {
let body = '';
res.on('data', (c) => body += c);
res.on('end', () => {
try { resolve(JSON.parse(body)); }
catch { reject(new Error('Invalid JSON')); }
});
}).on('error', reject);
});
}
_post(path, data) {
return new Promise((resolve, reject) => {
const body = JSON.stringify(data);
const req = http.request(`${BRIDGE_BASE}${path}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(body),
},
}, (res) => {
let resBody = '';
res.on('data', (c) => resBody += c);
res.on('end', () => {
try { resolve(JSON.parse(resBody)); }
catch { reject(new Error('Invalid JSON')); }
});
});
req.on('error', reject);
req.write(body);
req.end();
});
}
}
module.exports = BridgeClient;