first commit
This commit is contained in:
148
static/js/websocket.js
Normal file
148
static/js/websocket.js
Normal file
@@ -0,0 +1,148 @@
|
||||
/**
|
||||
* StockWebSocket - 키움 주식 실시간 시세 WebSocket 클라이언트
|
||||
* 자동 재연결 (지수 백오프), 구독 목록 자동 복구 지원
|
||||
*/
|
||||
class StockWebSocket {
|
||||
constructor() {
|
||||
this.ws = null;
|
||||
this.subscriptions = new Set(); // 현재 구독 중인 종목 코드
|
||||
// 메시지 타입별 핸들러 맵: type → { code → callbacks[] }
|
||||
this.handlers = {
|
||||
price: new Map(),
|
||||
orderbook: new Map(),
|
||||
program: new Map(),
|
||||
meta: new Map(),
|
||||
};
|
||||
// 전역 핸들러 (코드 무관한 메시지용)
|
||||
this.globalHandlers = {
|
||||
market: [],
|
||||
};
|
||||
this.reconnectDelay = 1000; // 초기 재연결 대기 시간 (ms)
|
||||
this.maxReconnectDelay = 30000; // 최대 재연결 대기 시간 (ms)
|
||||
this.intentionalClose = false;
|
||||
this.connect();
|
||||
}
|
||||
|
||||
connect() {
|
||||
const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
const url = `${protocol}//${location.host}/ws`;
|
||||
|
||||
this.ws = new WebSocket(url);
|
||||
|
||||
this.ws.onopen = () => {
|
||||
console.log('WebSocket 연결됨');
|
||||
this.reconnectDelay = 1000; // 성공 시 재연결 대기 시간 초기화
|
||||
|
||||
// 기존 구독 목록 자동 복구
|
||||
this.subscriptions.forEach(code => this._send({ type: 'subscribe', code }));
|
||||
};
|
||||
|
||||
this.ws.onmessage = (event) => {
|
||||
try {
|
||||
const msg = JSON.parse(event.data);
|
||||
this._handleMessage(msg);
|
||||
} catch (e) {
|
||||
console.error('메시지 파싱 실패:', e);
|
||||
}
|
||||
};
|
||||
|
||||
this.ws.onclose = () => {
|
||||
if (!this.intentionalClose) {
|
||||
console.log(`WebSocket 연결 끊김. ${this.reconnectDelay}ms 후 재연결...`);
|
||||
setTimeout(() => this.connect(), this.reconnectDelay);
|
||||
// 지수 백오프
|
||||
this.reconnectDelay = Math.min(this.reconnectDelay * 2, this.maxReconnectDelay);
|
||||
}
|
||||
};
|
||||
|
||||
this.ws.onerror = (err) => {
|
||||
console.error('WebSocket 오류:', err);
|
||||
};
|
||||
}
|
||||
|
||||
// 종목 구독
|
||||
subscribe(code) {
|
||||
this.subscriptions.add(code);
|
||||
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
||||
this._send({ type: 'subscribe', code });
|
||||
}
|
||||
}
|
||||
|
||||
// 종목 구독 해제
|
||||
unsubscribe(code) {
|
||||
this.subscriptions.delete(code);
|
||||
Object.values(this.handlers).forEach(map => map.delete(code));
|
||||
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
||||
this._send({ type: 'unsubscribe', code });
|
||||
}
|
||||
}
|
||||
|
||||
// 현재가 수신 콜백 등록
|
||||
onPrice(code, callback) {
|
||||
this._addCodeHandler('price', code, callback);
|
||||
}
|
||||
|
||||
// 호가창 수신 콜백 등록
|
||||
onOrderBook(code, callback) {
|
||||
this._addCodeHandler('orderbook', code, callback);
|
||||
}
|
||||
|
||||
// 프로그램 매매 수신 콜백 등록
|
||||
onProgram(code, callback) {
|
||||
this._addCodeHandler('program', code, callback);
|
||||
}
|
||||
|
||||
// 종목 메타 수신 콜백 등록
|
||||
onMeta(code, callback) {
|
||||
this._addCodeHandler('meta', code, callback);
|
||||
}
|
||||
|
||||
// 장운영 상태 수신 콜백 등록 (전역)
|
||||
onMarket(callback) {
|
||||
this.globalHandlers.market.push(callback);
|
||||
}
|
||||
|
||||
// 내부: 코드별 핸들러 등록
|
||||
_addCodeHandler(type, code, callback) {
|
||||
if (!this.handlers[type]) return;
|
||||
if (!this.handlers[type].has(code)) {
|
||||
this.handlers[type].set(code, []);
|
||||
}
|
||||
this.handlers[type].get(code).push(callback);
|
||||
}
|
||||
|
||||
// 내부: 메시지 처리
|
||||
_handleMessage(msg) {
|
||||
const { type, code, data } = msg;
|
||||
|
||||
if (type === 'market') {
|
||||
this.globalHandlers.market.forEach(fn => fn(data));
|
||||
return;
|
||||
}
|
||||
|
||||
if (type === 'error') {
|
||||
console.warn(`서버 오류 [${code}]:`, data?.message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.handlers[type] && code) {
|
||||
const callbacks = this.handlers[type].get(code) || [];
|
||||
callbacks.forEach(fn => fn(data));
|
||||
}
|
||||
}
|
||||
|
||||
// 내부: JSON 메시지 전송
|
||||
_send(data) {
|
||||
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
||||
this.ws.send(JSON.stringify(data));
|
||||
}
|
||||
}
|
||||
|
||||
close() {
|
||||
this.intentionalClose = true;
|
||||
this.ws?.close();
|
||||
}
|
||||
}
|
||||
|
||||
// 전역 인스턴스 (stock_detail.html에서 사용)
|
||||
const stockWS = new StockWebSocket();
|
||||
Reference in New Issue
Block a user