$.fn.websocket = function(url) { if (!url) return console.log('접속주소오류'); const $obj = $(this); // 초기 상태 변수 $obj.data('ws', null); $obj.data('reconnectTimeout', null); $obj.data('subscriptions', []); $obj.data('messageQueue', []); $obj.data('flushTimer', null); // DOM 제거 감지 (웹소켓 단위로 1회만 실행) const observer = new MutationObserver(mutations => { mutations.forEach(mutation => { mutation.removedNodes.forEach(removed => { if (removed.contains?.($obj[0])) { console.log("WebSocket 대상 제거됨 → 연결 종료") $obj[0].close?.(true) observer.disconnect() } }) }) }) observer.observe(document.body, { childList: true, subtree: true }) function isSocketOpen() { const ws = $obj.data('ws'); return ws && ws.readyState === WebSocket.OPEN; } function flushMessageQueue() { const queue = $obj.data('messageQueue'); if ($obj.data('flushTimer')) return; function flush() { $obj.removeData('flushTimer'); while (isSocketOpen() && queue.length > 0) { const message = queue.shift(); try { $obj.data('ws').send(message); console.log("큐에서 송신:", message); } catch (e) { console.error("송신 실패:", message, e); queue.unshift(message); break; } } if (queue.length > 0) { const timer = setTimeout(flush, 50); $obj.data('flushTimer', timer); } } const timer = setTimeout(flush, 0); $obj.data('flushTimer', timer); } function connectWebSocket() { if (isSocketOpen()) return; let ws = new WebSocket(url); $obj.data('ws', ws); ws.onopen = function() { console.log("서버에 연결됨"); $obj.data('subscriptions').forEach(function(path) { const msg = `add:${path}`; ws.send(msg); }); flushMessageQueue(); $obj.triggerHandler('ws.open'); }; ws.onmessage = function(event) { $obj.triggerHandler('ws.recv', event.data); } ws.onclose = function() { console.log("서버 연결 종료"); $obj.triggerHandler('ws.close'); reconnectWebSocket(); } ws.onerror = function(error) { console.error("WebSocket 오류:", error); ws.close(); } const el = $obj[0]; el.ws = ws; el.close = function(remove) { console.log("el.ws 상태:", el.ws) if (el.ws) { if (remove) el.ws.onclose = null el.ws.close() el.ws = null } } } function reconnectWebSocket() { clearTimeout($obj.data('reconnectTimeout')); const retry = setTimeout(() => { console.log("재연결 시도 중..."); connectWebSocket(); }, Math.floor(Math.random() * 5000)); $obj.data('reconnectTimeout', retry); } function send(message) { if (isSocketOpen()) { $obj.data('ws').send(message); } else { $obj.data('messageQueue').push(message); flushMessageQueue(); } } $obj[0].isReady = () => isSocketOpen(); $obj[0].send = (message) => send(message); $obj[0].path = function(path, add) { let msg; const subs = $obj.data('subscriptions'); const index = subs.indexOf(path); if (add && index === -1) { subs.push(path); msg = `add:${path}`; } else if (!add && index !== -1) { subs.splice(index, 1); msg = `del:${path}`; } if (msg) send(msg); } // $obj[0].close = function(remove) { // const ws = $obj.data('ws'); // // console.log( ws ); // if (ws) { // if (remove) ws.onclose = null; // ws.close(); // $obj.data('ws', null); // } // }; connectWebSocket(); return $obj; }