const { createApp, ref, computed, onMounted, onUnmounted } = Vue; createApp({ setup() { const status = ref({ stream: 'disconnected', bars_held: {} }); const price = ref(null); const now = ref(Date.now()); let chartApi = null; let socket = null; let timer = null; const barAge = computed(() => { if (!status.value.last_bar_t) return '—'; const seconds = Math.max(0, Math.floor(now.value / 1000 - status.value.last_bar_t)); return seconds < 60 ? `${seconds}s` : `${Math.floor(seconds / 60)}m`; }); async function refreshStatus() { const response = await fetch('/api/status'); if (response.ok) status.value = await response.json(); } function connect() { const protocol = location.protocol === 'https:' ? 'wss' : 'ws'; socket = new WebSocket(`${protocol}://${location.host}/ws`); socket.onopen = () => socket.send(JSON.stringify({ type: 'subscribe', tf: '1m' })); socket.onmessage = ({ data }) => { const message = JSON.parse(data); if (message.type === 'snapshot') { chartApi.setBars(message.bars); price.value = message.price; } else if (message.type === 'bar') { chartApi.updateBar(message.bar); price.value = message.bar.c; status.value.last_bar_t = message.bar.t; } }; socket.onclose = () => { status.value.stream = 'disconnected'; setTimeout(connect, 2000); }; } onMounted(() => { chartApi = new ConfluenceChart(); chartApi.create(document.getElementById('chart')); refreshStatus(); connect(); timer = setInterval(() => { now.value = Date.now(); refreshStatus(); }, 5000); }); onUnmounted(() => { clearInterval(timer); if (socket) socket.close(); if (chartApi) chartApi.destroy(); }); return { status, price, barAge }; }, }).mount('#app');