앱 실시간 차트 갱신오류 수정
This commit is contained in:
@@ -10,7 +10,7 @@ import { getCandleStart, TF_SECONDS } from '../utils/upbitApi';
|
|||||||
import { DISPLAY_COUNT, WARMUP_COUNT, OBV_DEEP_HISTORY } from './useUpbitData';
|
import { DISPLAY_COUNT, WARMUP_COUNT, OBV_DEEP_HISTORY } from './useUpbitData';
|
||||||
import type { WsStatus } from './useUpbitData';
|
import type { WsStatus } from './useUpbitData';
|
||||||
import { fetchBacktestHistoryCandles } from '../utils/backtestWarmup';
|
import { fetchBacktestHistoryCandles } from '../utils/backtestWarmup';
|
||||||
import { API_BASE } from '../utils/backendApi';
|
import { pinCandleWatch } from '../utils/backendApi';
|
||||||
|
|
||||||
/** 백엔드 BarBuilder 는 틱마다 1m 만 STOMP 발행 — 차트 분봉은 클라이언트에서 집계 */
|
/** 백엔드 BarBuilder 는 틱마다 1m 만 STOMP 발행 — 차트 분봉은 클라이언트에서 집계 */
|
||||||
const STOMP_TICK_CANDLE_TYPE = '1m';
|
const STOMP_TICK_CANDLE_TYPE = '1m';
|
||||||
@@ -28,13 +28,7 @@ function resolveChartBarTime(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function pinChartWatch(market: string, candleType: string): Promise<void> {
|
async function pinChartWatch(market: string, candleType: string): Promise<void> {
|
||||||
try {
|
await pinCandleWatch(market, candleType);
|
||||||
await fetch(`${API_BASE}/candles/watch?market=${encodeURIComponent(market)}&type=${encodeURIComponent(candleType)}`, {
|
|
||||||
method: 'POST',
|
|
||||||
});
|
|
||||||
} catch {
|
|
||||||
/* watch 실패해도 STOMP 구독은 계속 */
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface StompCandlePayload {
|
interface StompCandlePayload {
|
||||||
|
|||||||
@@ -22,16 +22,41 @@ export function getApiBase(): string {
|
|||||||
return API_BASE.replace(/\/api$/, '');
|
return API_BASE.replace(/\/api$/, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isDesktopRuntime(): boolean {
|
||||||
|
if (typeof window === 'undefined') return false;
|
||||||
|
if ('__TAURI__' in window || '__TAURI_INTERNALS__' in window) return true;
|
||||||
|
return document.documentElement.classList.contains('desktop-client');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** secure WebView(https) — ws:// mixed content 회피 */
|
||||||
|
export function getSecureApiOrigin(): string {
|
||||||
|
const origin = getApiBase().replace(/\/$/, '');
|
||||||
|
const pageSecure =
|
||||||
|
typeof window !== 'undefined'
|
||||||
|
&& (window.location.protocol === 'https:' || isDesktopRuntime());
|
||||||
|
if (pageSecure && /^http:\/\//i.test(origin)) {
|
||||||
|
return origin.replace(/^http:/i, 'https:');
|
||||||
|
}
|
||||||
|
return origin;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* STOMP SockJS 엔드포인트.
|
* STOMP SockJS 엔드포인트.
|
||||||
* Spring Boot context-path가 /api 이므로 반드시 /api/ws/trading 경로를 사용한다.
|
* Spring Boot context-path가 /api 이므로 반드시 /api/ws/trading 경로를 사용한다.
|
||||||
*/
|
*/
|
||||||
export function getStompSockJsUrl(): string {
|
export function getStompSockJsUrl(): string {
|
||||||
|
if (isDesktopRuntime()) {
|
||||||
|
return `${getSecureApiOrigin()}/api/ws/trading`;
|
||||||
|
}
|
||||||
const base = API_BASE.replace(/\/$/, '');
|
const base = API_BASE.replace(/\/$/, '');
|
||||||
const path = base.endsWith('/api') ? `${base}/ws/trading` : `${base}/api/ws/trading`;
|
const path = base.endsWith('/api') ? `${base}/ws/trading` : `${base}/api/ws/trading`;
|
||||||
// SockJS: 배포 환경에서 상대 경로만 쓰면 프록시/오리진 불일치 시 연결 실패가 난다.
|
// SockJS: 배포 환경에서 상대 경로만 쓰면 프록시/오리진 불일치 시 연결 실패가 난다.
|
||||||
if (typeof window !== 'undefined' && window.location?.origin) {
|
if (typeof window !== 'undefined' && window.location?.origin) {
|
||||||
return `${window.location.origin}${path.startsWith('/') ? path : `/${path}`}`;
|
const rel = path.startsWith('/') ? path : `/${path}`;
|
||||||
|
if (window.location.protocol === 'https:' && /^http:\/\//i.test(path)) {
|
||||||
|
return `${getSecureApiOrigin()}/api/ws/trading`;
|
||||||
|
}
|
||||||
|
return `${window.location.origin}${rel}`;
|
||||||
}
|
}
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
* 훅·차트마다 WebSocket 을 열면 모바일에서 429·재연결 폭주가 발생한다.
|
* 훅·차트마다 WebSocket 을 열면 모바일에서 429·재연결 폭주가 발생한다.
|
||||||
*/
|
*/
|
||||||
import type { UpbitMessage } from './upbitApi';
|
import type { UpbitMessage } from './upbitApi';
|
||||||
|
import { isDesktop } from './platform';
|
||||||
|
import { getSecureApiOrigin } from './backendApi';
|
||||||
|
|
||||||
export type UpbitWsChannel = 'ticker' | 'orderbook' | 'trade';
|
export type UpbitWsChannel = 'ticker' | 'orderbook' | 'trade';
|
||||||
|
|
||||||
@@ -36,6 +38,11 @@ let closingIntentionally = false;
|
|||||||
let wsFailureLogged = false;
|
let wsFailureLogged = false;
|
||||||
|
|
||||||
function getWsUrl(): string {
|
function getWsUrl(): string {
|
||||||
|
if (isDesktop()) {
|
||||||
|
const origin = getSecureApiOrigin().replace(/^http:\/\//i, 'https://');
|
||||||
|
const host = origin.replace(/^https:\/\//i, '');
|
||||||
|
return `wss://${host}/upbit-ws`;
|
||||||
|
}
|
||||||
const proto = window.location.protocol === 'https:' ? 'wss' : 'ws';
|
const proto = window.location.protocol === 'https:' ? 'wss' : 'ws';
|
||||||
return `${proto}://${window.location.host}/upbit-ws`;
|
return `${proto}://${window.location.host}/upbit-ws`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,13 +77,28 @@ export function getApiBase(): string {
|
|||||||
return API_BASE.replace(/\/api$/, '');
|
return API_BASE.replace(/\/api$/, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Desktop Tauri WebView(https 커스텀 스킴)에서는 ws://·http API XHR 이 mixed content 로 차단된다.
|
||||||
|
* exdev REST 는 tauri HTTP 로 우회하지만 SockJS/WebSocket 은 페이지 오리진 정책을 따른다.
|
||||||
|
*/
|
||||||
|
export function getSecureApiOrigin(): string {
|
||||||
|
const origin = getApiBase().replace(/\/$/, '');
|
||||||
|
const pageSecure =
|
||||||
|
typeof window !== 'undefined'
|
||||||
|
&& (window.location.protocol === 'https:' || isDesktopClient());
|
||||||
|
if (pageSecure && /^http:\/\//i.test(origin)) {
|
||||||
|
return origin.replace(/^http:/i, 'https:');
|
||||||
|
}
|
||||||
|
return origin;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* STOMP SockJS 엔드포인트.
|
* STOMP SockJS 엔드포인트.
|
||||||
* Spring Boot context-path가 /api 이므로 반드시 /api/ws/trading 경로를 사용한다.
|
* Spring Boot context-path가 /api 이므로 반드시 /api/ws/trading 경로를 사용한다.
|
||||||
*/
|
*/
|
||||||
export function getStompSockJsUrl(): string {
|
export function getStompSockJsUrl(): string {
|
||||||
const base = API_BASE.replace(/\/$/, '');
|
const secureOrigin = getSecureApiOrigin();
|
||||||
return base.endsWith('/api') ? `${base}/ws/trading` : `${base}/api/ws/trading`;
|
return `${secureOrigin}/api/ws/trading`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// UUID 생성 — HTTPS/localhost: crypto.randomUUID() 사용, HTTP: 폴리필
|
// UUID 생성 — HTTPS/localhost: crypto.randomUUID() 사용, HTTP: 폴리필
|
||||||
|
|||||||
Reference in New Issue
Block a user