const CACHE_NAME = 'v1'; importScripts('http://devtalk.kospo.co.kr:3000/static/js/module/stomp.js'); let sockJS = null; let stompClient = null; let reConnectAttempts = 0 let maxReconnectAttempts = 3 // 마스터 탭 변수 let connTab = null let messageList = []; let forceConnect = false; // 서비스워커 체커 마지막 시간 변수 let lastHeartbeat = null; /*재귀 함수 변수*/ let checker; let connectLock = false; let subscribe = null; // indexeddb 데이터 조회 function getData(col, val) { return new Promise((resolve, reject) => { if (connTab[col] === val) { resolve(connTab); } }) } // indexeddb 데이터 추가 function addData(obj) { connTab = obj; } function delData(tabId) { return new Promise((resolve, reject) => { messageList.forEach((message, idx) => { if (message.tabId === tabId) { messageList.splice(idx, 1); } }) if (connTab) { if (connTab.tabId === tabId) { connTab = null } } resolve(); }) } async function getClientCount(url) { const allClients = await clients.matchAll({ includeUncontrolled: true, type: 'window', }); const cc = allClients.filter(client => client.url === url); console.log(cc, '해당 클라이언트가 하나만 나와야함') return cc.length; } // 클라이언트 에게 메시지 전달 async function hubToClients(message) { const allClients = await clients.matchAll({ includeUncontrolled: true, type: 'window', }); allClients.forEach(client => { client.postMessage(message); }); } // 브라우저 종료시 소켓 연결 해지 async function disConnectWebSocket(message) { delData(message.tabId).then(() => { console.log('disConnect', connTab, message.tabId) if (connTab === null) { try { allClose(); if (messageList.length !== 0) connectWebSocket(messageList[0]) } catch (e) { } } }) // 모든 클라이언트가 종료시 서비스워커 heartbeat 제거 const allClients = await clients.matchAll({ includeUncontrolled: true, type: 'window', }); if(allClients.length === 1) { clearInterval(checker) checker = null; } } // 브라우저 종료시 소켓 연결 해지 async function kill() { console.log('service worker kill'); connTab = null; messageList = []; clearInterval(checker) checker = null; allClose(); } // 탭간 공유 웹소켓 연결 관리 async function connectWebSocket(message) { getClientCount(message.site).then((windowClientCount) => { if(windowClientCount === 1) { connTab = null messageList = [] } lastHeartbeat = Date.now(); if (connTab === null || sockJS.readyState !== 1) { if(sockJS !== null) { if(sockJS.readyState === WebSocket.OPEN) { allClose(); } } sockJS = new WebSocket(`ws://devtalk.kospo.co.kr:3000/stomp/ws`); // sockJS = new SockJS( // `http://devtalk.kospo.co.kr:3000/stomp/talk`, { // Authentication: message.encSabun // }, { // transports: ['websocket'], // reconnectInterval: 500, // reconnectDelay: 500, // }, // ); if(sockJS !== WebSocket.CLOSED && sockJS !== WebSocket.CLOSING) { stompClient = Stomp.over(sockJS); stompClient.heartbeat.outgoing = 20000; stompClient.heartbeat.incoming = 20000; stompClient.debug = (e) => { if (e.includes('ERROR') || e.includes('Exception') || e.includes('failed')) { console.log(new Date().toString()) console.error('STOMP error:', e); } }; stompClient.connect({Authentication: message.encSabun, location: 'SW'}, function (frame) { console.log('hub connect', message.tabId) subscribe = stompClient.subscribe(`/exchange/hub.exchange/hub.${message.tabId}`, async function (content) { const payload = JSON.parse(content.body); hubToClients(payload) }) /*사이트 정보 탭 아이디 저장*/ addData({ url: message.site, tabId: message.tabId }) /* 여러개 탭이 활성화시 방지용 서비스워커 체커*/ if(checker == null) { /*테스트*/ checker = setInterval(() => { // lastHeartbeat = Date.now(); // console.log('서비스워커 실행중', new Date()) hubToClients({type:"PING"}) }, 10000) } else { console.log('checker and reset is exist') } // 재연결 시도 초기화 reConnectAttempts = 0; }) } const baseDelay = 500 sockJS.onclose = (e) => { if(e.code !== 1000) { const delay = Math.min(baseDelay * Math.pow(2, reConnectAttempts), 5000) setTimeout(() => { hubReconnect() if(reConnectAttempts < maxReconnectAttempts) { reConnectAttempts++; } else { console.log('[Service Worker Hub] Max Reconnect attempts reached.') } }, delay) } } } else { console.log('hub socket is connected') } }); } const hubReconnect = () => { allClose() clearInterval(checker) checker = null; hubToClients({type:'HUB_RECONNECT'}); lastHeartbeat = Date.now(); } // 서비스워커 설치 self.addEventListener('install', (event) => { console.log('SW: Installing...'); event.waitUntil( self.skipWaiting(), ); }); // 서비스워커 활성화 self.addEventListener('activate', function activator(event) { // delData(); event.waitUntil( self.clients.claim() ); }) // 클라이언트로부터의 메시지 처리 (연결 및 해지) self.addEventListener('message', async (event) => { const message = event.data; switch (message.type) { case 'CONNECT': connectLock = false; messageList.push(message); connectWebSocket(message); break; case 'TAB_CLOSE': disConnectWebSocket(message); break; case "KILL" : kill(); break; case 'PONG' : if (stompClient === null) { hubReconnect() } break; case "WAKEUP" : if (stompClient === null) { if (!connectLock) { connectLock = true; hubReconnect() } } else { if (!connectLock) { hubToClients({type: "GETUP"}) } } break; case 'HUB_RECONNECT_SUCCESS' : let suc = true; while (suc) { if (stompClient.connected) { connectLock = false; suc = false; } await sleep(100) } break; case 'FONT_CHANGE': hubToClients(message) break; } }); const sleep = (delay) => { return new Promise(resolve => setTimeout(resolve, delay)); } const allClose = () => { try{subscribe.unsubscribe()} catch(e) {} try{stompClient.disconnect()} catch(e) {} try{sockJS.close();} catch(e) {} stompClient = null; sockJS = null; subscribe = null; }