This commit is contained in:
2025-07-02 21:56:37 +09:00
commit 29adf2c274
736 changed files with 89300 additions and 0 deletions

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

BIN
public/img/icon.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
public/img/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

23
public/index.html Normal file
View File

@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<link rel="manifest" href="<%= BASE_URL %>manifest.json" crossorigin="anonymous">
<!-- <link href="../node_modules/simplebar/dist/simplebar.min.css" rel="stylesheet" type="text/css"> -->
<title>KOSPO 헬프톡</title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled.
Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

View File

@@ -0,0 +1,22 @@
const clients = [];
self.onconnect = function(e) {
console.log(1)
const port = e.ports[0];
clients.push(port)
port.onmessage = function(e) {
const message = e.data;
clients.forEach(client => client.postMessage(message))
}
port.isMaster = function() {
clients.forEach(client => client.postMessage({tabId: port.tabId}))
}
port.setMaster = function(tabId) {
port.tabId = tabId;
}
}

5232
public/js/sockjs.js Normal file

File diff suppressed because it is too large Load Diff

492
public/js/stomp.js Normal file
View File

@@ -0,0 +1,492 @@
// Generated by CoffeeScript 1.7.1
/*
Stomp Over WebSocket http://www.jmesnil.net/stomp-websocket/doc/ | Apache License V2.0
Copyright (C) 2010-2013 [Jeff Mesnil](http://jmesnil.net/)
Copyright (C) 2012 [FuseSource, Inc.](http://fusesource.com)
*/
(function () {
var Byte, Client, Frame, Stomp,
__hasProp = {}.hasOwnProperty,
__slice = [].slice;
Byte = {
LF: '\x0A',
NULL: '\x00'
};
Frame = (function () {
var unmarshallSingle;
function Frame(command, headers, body) {
this.command = command;
this.headers = headers != null ? headers : {};
this.body = body != null ? body : '';
}
Frame.prototype.toString = function () {
var lines, name, skipContentLength, value, _ref;
lines = [this.command];
skipContentLength = this.headers['content-length'] === false ? true : false;
if (skipContentLength) {
delete this.headers['content-length'];
}
_ref = this.headers;
for (name in _ref) {
if (!__hasProp.call(_ref, name)) continue;
value = _ref[name];
lines.push("" + name + ":" + value);
}
if (this.body && !skipContentLength) {
lines.push("content-length:" + (Frame.sizeOfUTF8(this.body)));
}
lines.push(Byte.LF + this.body);
return lines.join(Byte.LF);
};
Frame.sizeOfUTF8 = function (s) {
if (s) {
return encodeURI(s).match(/%..|./g).length;
} else {
return 0;
}
};
unmarshallSingle = function (data) {
var body, chr, command, divider, headerLines, headers, i, idx, len, line, start, trim, _i, _j, _len, _ref,
_ref1;
divider = data.search(RegExp("" + Byte.LF + Byte.LF));
headerLines = data.substring(0, divider).split(Byte.LF);
command = headerLines.shift();
headers = {};
trim = function (str) {
return str.replace(/^\s+|\s+$/g, '');
};
_ref = headerLines.reverse();
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
line = _ref[_i];
idx = line.indexOf(':');
headers[trim(line.substring(0, idx))] = trim(line.substring(idx + 1));
}
body = '';
start = divider + 2;
if (headers['content-length']) {
len = parseInt(headers['content-length']);
body = ('' + data).substring(start, start + len);
} else {
chr = null;
for (i = _j = start, _ref1 = data.length; start <= _ref1 ? _j < _ref1 : _j > _ref1; i = start <= _ref1 ? ++_j : --_j) {
chr = data.charAt(i);
if (chr === Byte.NULL) {
break;
}
body += chr;
}
}
return new Frame(command, headers, body);
};
Frame.unmarshall = function (datas) {
var data;
return (function () {
var _i, _len, _ref, _results;
_ref = datas.split(RegExp("" + Byte.NULL + Byte.LF + "*"));
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
data = _ref[_i];
if ((data != null ? data.length : void 0) > 0) {
_results.push(unmarshallSingle(data));
}
}
return _results;
})();
};
Frame.marshall = function (command, headers, body) {
var frame;
frame = new Frame(command, headers, body);
return frame.toString() + Byte.NULL;
};
return Frame;
})();
Client = (function () {
var now;
function Client(ws) {
this.ws = ws;
this.ws.binaryType = "arraybuffer";
this.counter = 0;
this.connected = false;
this.heartbeat = {
outgoing: 10000,
incoming: 10000
};
this.maxWebSocketFrameSize = 16 * 1024;
this.subscriptions = {};
}
Client.prototype.debug = function (message) {
var _ref;
return typeof window !== "undefined" && window !== null ? (_ref = window.console) != null ? _ref.log(message) : void 0 : void 0;
};
now = function () {
if (Date.now) {
return Date.now();
} else {
return new Date().valueOf;
}
};
Client.prototype._transmit = function (command, headers, body) {
var out;
out = Frame.marshall(command, headers, body);
if (typeof this.debug === "function") {
this.debug(">>> " + out);
}
while (true) {
if (out.length > this.maxWebSocketFrameSize) {
this.ws.send(out.substring(0, this.maxWebSocketFrameSize));
out = out.substring(this.maxWebSocketFrameSize);
if (typeof this.debug === "function") {
this.debug("remaining = " + out.length);
}
} else {
return this.ws.send(out);
}
}
};
Client.prototype._setupHeartbeat = function (headers) {
var serverIncoming, serverOutgoing, ttl, v, _ref, _ref1;
if ((_ref = headers.version) !== Stomp.VERSIONS.V1_1 && _ref !== Stomp.VERSIONS.V1_2) {
return;
}
_ref1 = (function () {
var _i, _len, _ref1, _results;
_ref1 = headers['heart-beat'].split(",");
_results = [];
for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
v = _ref1[_i];
_results.push(parseInt(v));
}
return _results;
})(), serverOutgoing = _ref1[0], serverIncoming = _ref1[1];
if (!(this.heartbeat.outgoing === 0 || serverIncoming === 0)) {
ttl = Math.max(this.heartbeat.outgoing, serverIncoming);
if (typeof this.debug === "function") {
this.debug("send PING every " + ttl + "ms");
}
this.pinger = Stomp.setInterval(ttl, (function (_this) {
return function () {
_this.ws.send(Byte.LF);
return typeof _this.debug === "function" ? _this.debug(">>> PING") : void 0;
};
})(this));
}
if (!(this.heartbeat.incoming === 0 || serverOutgoing === 0)) {
ttl = Math.max(this.heartbeat.incoming, serverOutgoing);
if (typeof this.debug === "function") {
this.debug("check PONG every " + ttl + "ms");
}
return this.ponger = Stomp.setInterval(ttl, (function (_this) {
return function () {
var delta;
delta = now() - _this.serverActivity;
if (delta > ttl * 2) {
if (typeof _this.debug === "function") {
_this.debug("did not receive server activity for the last " + delta + "ms");
}
return _this.ws.close();
}
};
})(this));
}
};
Client.prototype._parseConnect = function () {
var args, connectCallback, errorCallback, headers;
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
headers = {};
switch (args.length) {
case 2:
headers = args[0], connectCallback = args[1];
break;
case 3:
if (args[1] instanceof Function) {
headers = args[0], connectCallback = args[1], errorCallback = args[2];
} else {
headers.login = args[0], headers.passcode = args[1], connectCallback = args[2];
}
break;
case 4:
headers.login = args[0], headers.passcode = args[1], connectCallback = args[2], errorCallback = args[3];
break;
default:
headers.login = args[0], headers.passcode = args[1], connectCallback = args[2], errorCallback = args[3], headers.host = args[4];
}
return [headers, connectCallback, errorCallback];
};
Client.prototype.connect = function () {
var args, errorCallback, headers, out;
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
out = this._parseConnect.apply(this, args);
headers = out[0], this.connectCallback = out[1], errorCallback = out[2];
if (typeof this.debug === "function") {
this.debug("Opening Web Socket...");
}
this.ws.onmessage = (function (_this) {
return function (evt) {
var arr, c, client, data, frame, messageID, onreceive, subscription, _i, _len, _ref, _results;
data = typeof ArrayBuffer !== 'undefined' && evt.data instanceof ArrayBuffer ? (arr = new Uint8Array(evt.data), typeof _this.debug === "function" ? _this.debug("--- got data length: " + arr.length) : void 0, ((function () {
var _i, _len, _results;
_results = [];
for (_i = 0, _len = arr.length; _i < _len; _i++) {
c = arr[_i];
_results.push(String.fromCharCode(c));
}
return _results;
})()).join('')) : evt.data;
_this.serverActivity = now();
if (data === Byte.LF) {
if (typeof _this.debug === "function") {
_this.debug("<<< PONG");
}
return;
}
if (typeof _this.debug === "function") {
_this.debug("<<< " + data);
}
_ref = Frame.unmarshall(data);
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
frame = _ref[_i];
switch (frame.command) {
case "CONNECTED":
if (typeof _this.debug === "function") {
_this.debug("connected to server " + frame.headers.server);
}
_this.connected = true;
_this._setupHeartbeat(frame.headers);
_results.push(typeof _this.connectCallback === "function" ? _this.connectCallback(frame) : void 0);
break;
case "MESSAGE":
subscription = frame.headers.subscription;
onreceive = _this.subscriptions[subscription] || _this.onreceive;
if (onreceive) {
client = _this;
messageID = frame.headers["message-id"];
frame.ack = function (headers) {
if (headers == null) {
headers = {};
}
return client.ack(messageID, subscription, headers);
};
frame.nack = function (headers) {
if (headers == null) {
headers = {};
}
return client.nack(messageID, subscription, headers);
};
_results.push(onreceive(frame));
} else {
_results.push(typeof _this.debug === "function" ? _this.debug("Unhandled received MESSAGE: " + frame) : void 0);
}
break;
case "RECEIPT":
_results.push(typeof _this.onreceipt === "function" ? _this.onreceipt(frame) : void 0);
break;
case "ERROR":
_results.push(typeof errorCallback === "function" ? errorCallback(frame) : void 0);
break;
default:
_results.push(typeof _this.debug === "function" ? _this.debug("Unhandled frame: " + frame) : void 0);
}
}
return _results;
};
})(this);
this.ws.onclose = (function (_this) {
return function () {
var msg;
msg = "Whoops! Lost connection to " + _this.ws.url;
if (typeof _this.debug === "function") {
_this.debug(msg);
}
_this._cleanUp();
return typeof errorCallback === "function" ? errorCallback(msg) : void 0;
};
})(this);
return this.ws.onopen = (function (_this) {
return function () {
if (typeof _this.debug === "function") {
_this.debug('Web Socket Opened...');
}
headers["accept-version"] = Stomp.VERSIONS.supportedVersions();
headers["heart-beat"] = [_this.heartbeat.outgoing, _this.heartbeat.incoming].join(',');
return _this._transmit("CONNECT", headers);
};
})(this);
};
Client.prototype.disconnect = function (disconnectCallback, headers) {
if (headers == null) {
headers = {};
}
this._transmit("DISCONNECT", headers);
this.ws.onclose = null;
this.ws.close();
this._cleanUp();
return typeof disconnectCallback === "function" ? disconnectCallback() : void 0;
};
Client.prototype._cleanUp = function () {
this.connected = false;
if (this.pinger) {
Stomp.clearInterval(this.pinger);
}
if (this.ponger) {
return Stomp.clearInterval(this.ponger);
}
};
Client.prototype.send = function (destination, headers, body) {
if (headers == null) {
headers = {};
}
if (body == null) {
body = '';
}
headers.destination = destination;
return this._transmit("SEND", headers, body);
};
Client.prototype.subscribe = function (destination, callback, headers) {
var client;
if (headers == null) {
headers = {};
}
if (!headers.id) {
headers.id = "sub-" + this.counter++;
}
headers.destination = destination;
this.subscriptions[headers.id] = callback;
this._transmit("SUBSCRIBE", headers);
client = this;
return {
id: headers.id,
unsubscribe: function () {
return client.unsubscribe(headers.id);
}
};
};
Client.prototype.unsubscribe = function (id) {
delete this.subscriptions[id];
return this._transmit("UNSUBSCRIBE", {
id: id
});
};
Client.prototype.begin = function (transaction) {
var client, txid;
txid = transaction || "tx-" + this.counter++;
this._transmit("BEGIN", {
transaction: txid
});
client = this;
return {
id: txid,
commit: function () {
return client.commit(txid);
},
abort: function () {
return client.abort(txid);
}
};
};
Client.prototype.commit = function (transaction) {
return this._transmit("COMMIT", {
transaction: transaction
});
};
Client.prototype.abort = function (transaction) {
return this._transmit("ABORT", {
transaction: transaction
});
};
Client.prototype.ack = function (messageID, subscription, headers) {
if (headers == null) {
headers = {};
}
headers["message-id"] = messageID;
headers.subscription = subscription;
return this._transmit("ACK", headers);
};
Client.prototype.nack = function (messageID, subscription, headers) {
if (headers == null) {
headers = {};
}
headers["message-id"] = messageID;
headers.subscription = subscription;
return this._transmit("NACK", headers);
};
return Client;
})();
Stomp = {
VERSIONS: {
V1_0: '1.0',
V1_1: '1.1',
V1_2: '1.2',
supportedVersions: function () {
return '1.1,1.0';
}
},
client: function (url, protocols) {
var klass, ws;
if (protocols == null) {
protocols = ['v10.stomp', 'v11.stomp'];
}
klass = Stomp.WebSocketClass || WebSocket;
ws = new klass(url, protocols);
return new Client(ws);
},
over: function (ws) {
return new Client(ws);
},
Frame: Frame
};
if (typeof window !== "undefined" && window !== null) {
Stomp.setInterval = function (interval, f) {
return window.setInterval(f, interval);
};
Stomp.clearInterval = function (id) {
return window.clearInterval(id);
};
window.Stomp = Stomp;
} else {
Stomp.setInterval = function (interval, f) {
return setInterval(f, interval);
}
Stomp.clearInterval = function (id) {
return clearInterval(id);
};
self.Stomp = Stomp;
}
}).call(this);

26
public/manifest.json Normal file
View File

@@ -0,0 +1,26 @@
{
"name": "KOSPO 헬프톡",
"id" : "helptalk",
"start_url": "/",
"display": "fullscreen",
"display_override": ["window-controls-overlay"],
"theme-color": "#FF0000",
"protocol_handlers" : [
{
"protocol" : "web+helptalk",
"url" : "/%s"
}
],
"icons": [
{
"src": "/img/icon.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "/img/icon.png",
"type": "image/png",
"sizes": "512x512"
}
]
}

202
public/service-worker.js Normal file
View File

@@ -0,0 +1,202 @@
// eslint-disable-next-line no-undef
importScripts(`/js/stomp.js`)
// eslint-disable-next-line no-undef
importScripts(`/js/sockjs.js`)
let sockJS;
let stompClient;
let reConnectAttempts = 0
let maxReconnectAttempts = 3
let messageList = []
let connectLock = false;
let lastHeartbeat = null;
/*재귀 함수 변수*/
let sendPing= null;
/*소켓 연결 변수*/
let subscribe = null;
// 클라이언트 에게 메시지 전달
async function hubToClients(message) {
const allClients = await clients.matchAll({
includeUncontrolled: true,
type: 'window',
});
allClients.forEach(client => {
client.postMessage(message);
});
}
// 브라우저 종료시 소켓 연결 해지
async function disConnectWebSocket(message) {
console.log('disConnect', message)
try {subscribe.unsubscribe()} catch (e) {}
try {stompClient.disconnect()} catch (e) {}
try {sockJS.close()} catch (e) {}
sockJS = null
stompClient = null
// 모든 클라이언트가 종료시 서비스워커 heartbeat 제거
const allClients = await clients.matchAll({
includeUncontrolled: true,
type: 'window',
});
if(allClients.length === 1) {
clearInterval(sendPing)
sendPing = null;
}
}
// 브라우저 종료시 소켓 연결 해지
async function kill() {
console.log('service worker stompClient', stompClient)
console.log('service worker sockJS', sockJS)
clearInterval(sendPing)
sendPing = null
try {subscribe.unsubscribe()} catch (e) {}
try {stompClient.disconnect()} catch (e) {}
try {sockJS.close()} catch (e) {}
stompClient = null;
sockJS = null;
}
// 웹소켓 연결 관리
function connectWebSocket(message) {
lastHeartbeat = Date.now();
console.log('sockJSConn',typeof (sockJS))
console.log('sockJSInfo',sockJS)
if (sockJS === null || sockJS === undefined) {
if(sockJS !== null && sockJS !== undefined) {
if(sockJS.readyState && sockJS.readyState === WebSocket.OPEN) {
try{subscribe.unsubscribe()} catch(e) {}
try{stompClient.disconnect();} catch(e) {}
try{sockJS.close();} catch(e) {}
stompClient = null;
sockJS = null;
}
}
sockJS = new WebSocket(`${message.url}/stomp/ws`);
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.error('STOMP error:', e, new Date().toString());
}
};
stompClient.connect({location: 'SW'}, function (frame) {
console.log('message.sabun', message.sabun, stompClient)
subscribe = stompClient.subscribe(`/exchange/user.exchange/user.${message.sabun}`, async (content) => {
const payload = JSON.parse(content.body);
console.log('servicew-usersubcribe', payload)
})
console.log('hub connect()')
if (sendPing == null) {
sendPing = setInterval(() => {
hubToClients({type: "PING"})
}, 10000);
}
reConnectAttempts = 0;
})
}
const baseDelay = 500
sockJS.onclose = (e) => {
console.error('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('socket is connected')
}
}
const hubReconnect = () => {
try {subscribe.unsubscribe()} catch (e) {}
try {stompClient.disconnect()} catch (e) {}
try {sockJS.close()} catch (e) {}
stompClient = null
sockJS = null
clearInterval(sendPing)
sendPing = null;
hubToClients({type:'RECONNECT'});
lastHeartbeat = Date.now();
}
// 서비스워커 설치
self.addEventListener('install', (event) => {
console.log('SW: Installing...');
event.waitUntil(
self.skipWaiting()
);
});
// 서비스워커 활성화
this.addEventListener('activate', function activator(event) {
console.log('activate!');
event.waitUntil(
self.clients.claim()
);
})
// 클라이언트로부터의 메시지 처리
self.addEventListener('message', async (event) => {
const message = event.data;
switch (message.type) {
case 'CONNECT':
connectWebSocket(message);
break;
case 'CLOSE':
disConnectWebSocket(message);
break;
case "KILL" :
kill();
break;
case 'PONG' :
if(typeof (stompClient) === "undefined") {
hubReconnect()
}
break;
case "WAKEUP" :
if(typeof (stompClient) === "undefined") {
if(!connectLock) {
connectLock = true;
hubReconnect()
}
} else {
if(!connectLock) {
hubToClients({type:"GETUP"})
}
}
break;
case 'RECONNECT_SUCCESS' :
let suc = true;
while (suc) {
if (stompClient.connected) {
connectLock = false;
suc = false;
}
await sleep(100)
}
break;
}
});
const sleep = (delay) => {
return new Promise(resolve => setTimeout(resolve, delay));
}