// GridTracker Copyright © 2023 GridTracker.org // All rights reserved. // See LICENSE for more information. GT.gtEngineInterval = null; GT.chatRecvFunctions = { uuid: gtChatSetUUID, list: gtChatNewList, info: gtChatUpdateCall, drop: gtChatRemoveCall, mesg: gtChatMessage, o: gtSpotMessage, ba: bandActivityReply }; var ChatState = Object(); ChatState.none = -1; ChatState.idle = 0; ChatState.connect = 1; ChatState.connecting = 2; ChatState.connected = 3; ChatState.waitUUID = 7; ChatState.status = 4; ChatState.closed = 5; ChatState.error = 6; GT.gtStateToFunction = { "-1": gtSetIdle, 0: gtCanConnect, 1: gtConnectChat, 2: gtConnecting, 3: gtChatSendUUID, 4: gtStatusCheck, 5: gtInError, 6: gtClosedSocket, 7: gtWaitUUID }; GT.gtChatSocket = null; GT.gtFlagPins = Object(); GT.gtMessages = Object(); GT.gtUnread = Object(); GT.gtCallsigns = Object(); GT.gtSentAwayToCid = Object(); GT.gtState = ChatState.none; GT.gtStatusCount = 0; GT.gtStatusTime = 500; GT.gtMaxChatMessages = 100; GT.gtNeedUsersList = true; GT.gtUuidValid = false; GT.gtLiveStatusUpdate = false; GT.oamsBandActivityData = null; var myChatId = 0; var myRoom = 0; GT.gtCurrentMessageCount = 0; function gtConnectChat() { if (GT.gtChatSocket != null) { // we should start over GT.gtState = ChatState.error; return; } var rnd = parseInt(Math.random() * 10) + 18360; try { GT.gtState = ChatState.connecting; GT.gtChatSocket = new WebSocket("ws://oams.space:" + rnd); } catch (e) { GT.gtState = ChatState.error; return; } GT.gtChatSocket.onopen = function () { GT.gtState = ChatState.connected; }; GT.gtChatSocket.onmessage = function (evt) { if (GT.appSettings.gtShareEnable == true) { let jsmesg = false; try { jsmesg = JSON.parse(evt.data); } catch (err) { // bad message, dumping client GT.gtState = ChatState.error; return; } if (!("type" in jsmesg)) { GT.gtState = ChatState.error; return; } if (jsmesg.type in GT.chatRecvFunctions) { GT.chatRecvFunctions[jsmesg.type](jsmesg); } else { // Not fatal! console.log("Unknown oams message '" + jsmesg.type + "' ignoring"); } } }; GT.gtChatSocket.onerror = function () { this.close(); GT.gtChatSocket = null; GT.gtState = ChatState.error; }; GT.gtChatSocket.onclose = function () { GT.gtChatSocket = null; GT.gtState = ChatState.closed; }; } function gtConnecting() {} function gtInError() { closeGtSocket(); } function gtChatSendClose() { msg = Object(); msg.type = "close"; msg.uuid = GT.appSettings.chatUUID; sendGtJson(JSON.stringify(msg)); } function closeGtSocket() { if (GT.gtChatSocket != null) { gtChatSendClose(); GT.gtChatSocket.close(); GT.gtChatSocket = null; GT.gtState = ChatState.none; } else GT.gtState = ChatState.none; } function gtClosedSocket() { if (GT.gtChatSocket != null) { GT.gtChatSocket.close(); GT.gtChatSocket = null; } GT.gtState = ChatState.none; } // Connect 15 seconds after startup GT.lastConnectAttempt = parseInt(Date.now() / 1000) - 15; function gtCanConnect() { GT.lastConnectAttempt = timeNowSec(); GT.gtState = ChatState.connect; } function gtSetIdle() { if (timeNowSec() - GT.lastConnectAttempt >= 30) { GT.gtStatusCount = 0; GT.gtNeedUsersList = true; GT.gtState = ChatState.idle; GT.lastGtStatus = ""; } GT.gtUuidValid = false; } function gtStatusCheck() { if (GT.gtStatusCount > 0) { GT.gtStatusCount--; } if (GT.gtStatusCount == 0 || GT.gtLiveStatusUpdate == true) { if (GT.gtLiveStatusUpdate == true) { GT.gtLiveStatusUpdate = false; } else { GT.lastGtStatus = ""; GT.gtStatusCount = GT.gtStatusTime; } gtChatSendStatus(); } if (GT.gtNeedUsersList == true) { GT.gtNeedUsersList = false; gtChatGetList(); } } function sendGtJson(json, isUUIDrequest = false) { if (GT.appSettings.gtShareEnable == true && GT.gtChatSocket != null) { if (GT.gtChatSocket.readyState == WebSocket.OPEN && (isUUIDrequest || GT.gtUuidValid)) { GT.gtChatSocket.send(json); } else { if (GT.gtChatSocket.readyState == WebSocket.CLOSED) { GT.gtState = ChatState.closed; } } } } GT.lastGtStatus = ""; function gtChatSendStatus() { var msg = Object(); msg.type = "status"; msg.uuid = GT.appSettings.chatUUID; msg.call = GT.appSettings.myCall; msg.grid = GT.appSettings.myRawGrid; msg.freq = GT.appSettings.myRawFreq; msg.mode = GT.appSettings.myMode; msg.band = GT.appSettings.myBand; msg.src = "GT"; msg.canmsg = GT.appSettings.gtMsgEnable; msg.o = GT.appSettings.gtSpotEnable == true ? 1 : 0; msg = JSON.stringify(msg); if (msg != GT.lastGtStatus) { sendGtJson(msg); GT.lastGtStatus = msg; } } function gtChatSendSpots(spotsObject, detailsObject) { let msg = Object(); msg.type = "o"; msg.uuid = GT.appSettings.chatUUID; msg.o = spotsObject; msg.d = detailsObject; sendGtJson(JSON.stringify(msg)); } function gtChatSendDecodes(instancesObject) { let msg = Object(); msg.type = "d"; msg.uuid = GT.appSettings.chatUUID; msg.i = instancesObject; sendGtJson(JSON.stringify(msg)); } function oamsBandActivityCheck() { if (GT.appSettings.oamsBandActivity == true && GT.appSettings.myGrid.length >= 4) { let grid = GT.appSettings.myGrid.substring(0, 4).toUpperCase(); if (GT.appSettings.oamsBandActivityNeighbors == true) { gtChatSendBandActivityRequest(squareToNeighbors(grid)); } else { gtChatSendBandActivityRequest([grid]); } } } function gtChatSendBandActivityRequest(gridArray) { msg = Object(); msg.type = "ba"; msg.uuid = GT.appSettings.chatUUID; msg.ga = gridArray; sendGtJson(JSON.stringify(msg)); } function bandActivityReply(jsmesg) { GT.oamsBandActivityData = jsmesg.r; renderBandActivity(); } function gtChatRemoveCall(jsmesg) { var id = jsmesg.id; var cid = jsmesg.cid; if (cid in GT.gtFlagPins) { if (id in GT.gtFlagPins[cid].ids) { delete GT.gtFlagPins[cid].ids[id]; } if (Object.keys(GT.gtFlagPins[cid].ids).length == 0) { delete GT.gtCallsigns[GT.gtFlagPins[cid].call][cid]; if (GT.gtFlagPins[cid].pin != null) { // remove pin from map here if (GT.layerSources.gtflags.hasFeature(GT.gtFlagPins[cid].pin)) { GT.layerSources.gtflags.removeFeature(GT.gtFlagPins[cid].pin); } delete GT.gtFlagPins[cid].pin; GT.gtFlagPins[cid].pin = null; } GT.gtFlagPins[cid].live = false; notifyNoChat(cid); if (!(cid in GT.gtMessages)) { if (Object.keys(GT.gtCallsigns[GT.gtFlagPins[cid].call]).length == 0) { delete GT.gtCallsigns[GT.gtFlagPins[cid].call]; } delete GT.gtFlagPins[cid]; } updateChatWindow(cid); } } } function gtChatUpdateCall(jsmesg) { var id = jsmesg.id; var cid = jsmesg.cid; if (cid in GT.gtFlagPins) { GT.gtFlagPins[cid].ids[id] = true; // Did they move grid location? if (jsmesg.grid != GT.gtFlagPins[cid].grid && GT.gtFlagPins[cid].pin != null) { // remove pin from map here if (GT.layerSources.gtflags.hasFeature(GT.gtFlagPins[cid].pin)) { GT.layerSources.gtflags.removeFeature(GT.gtFlagPins[cid].pin); } delete GT.gtFlagPins[cid].pin; GT.gtFlagPins[cid].pin = null; } // Changed callsign? if (GT.gtFlagPins[cid].call != jsmesg.call) { delete GT.gtCallsigns[GT.gtFlagPins[cid].call][cid]; } } else { GT.gtFlagPins[cid] = Object(); GT.gtFlagPins[cid].pin = null; GT.gtFlagPins[cid].ids = Object(); GT.gtFlagPins[cid].ids[id] = true; } GT.gtFlagPins[cid].cid = jsmesg.cid; GT.gtFlagPins[cid].call = jsmesg.call; GT.gtFlagPins[cid].fCall = formatCallsign(jsmesg.call); GT.gtFlagPins[cid].grid = jsmesg.grid; GT.gtFlagPins[cid].freq = jsmesg.freq; GT.gtFlagPins[cid].band = jsmesg.band; GT.gtFlagPins[cid].mode = jsmesg.mode; GT.gtFlagPins[cid].src = jsmesg.src; GT.gtFlagPins[cid].canmsg = jsmesg.canmsg; GT.gtFlagPins[cid].o = jsmesg.o; GT.gtFlagPins[cid].dxcc = callsignToDxcc(jsmesg.call); GT.gtFlagPins[cid].live = true; // Make a pin here if (GT.gtFlagPins[cid].pin == null) { makeGtPin(GT.gtFlagPins[cid]); if (GT.gtFlagPins[cid].pin != null) { GT.layerSources.gtflags.addFeature(GT.gtFlagPins[cid].pin); } } if (!(GT.gtFlagPins[cid].call in GT.gtCallsigns)) { // Can happen when a user changes callsign GT.gtCallsigns[GT.gtFlagPins[cid].call] = {}; } GT.gtCallsigns[GT.gtFlagPins[cid].call][cid] = true; updateChatWindow(cid); } function gtChatGetList() { msg = Object(); msg.type = "list"; msg.uuid = GT.appSettings.chatUUID; sendGtJson(JSON.stringify(msg)); } function redrawPins() { clearGtFlags(); for (cid in GT.gtFlagPins) { if (GT.gtFlagPins[cid].pin != null) { delete GT.gtFlagPins[cid].pin; GT.gtFlagPins[cid].pin = null; } makeGtPin(GT.gtFlagPins[cid]); if (GT.gtFlagPins[cid].pin != null) { GT.layerSources.gtflags.addFeature(GT.gtFlagPins[cid].pin); } } } function makeGtPin(obj) { try { if (obj.pin) { if (GT.layerSources.gtflags.hasFeature(obj.pin)) { GT.layerSources.gtflags.removeFeature(obj.pin); } delete obj.pin; obj.pin = null; } if (obj.src != "GT") return; if (typeof obj.grid == "undefined" || obj.grid == null) return; if (obj.grid.length != 4 && obj.grid.length != 6) return; if (validateGridFromString(obj.grid) == false) return; if (!validateMapBandAndMode(obj.band, obj.mode)) { return; } var LL = squareToCenter(obj.grid); obj.pin = iconFeature(ol.proj.fromLonLat([LL.o, LL.a]), GT.gtFlagIcon, 100, "gtFlag"); obj.pin.key = obj.cid; obj.pin.isGtFlag = true; obj.pin.size = 1; } catch (e) {} } function gtChatNewList(jsmesg) { clearGtFlags(); // starting clean if we're getting a new chat list GT.gtFlagPins = Object() GT.gtMessages = Object(); GT.gtUnread = Object(); GT.gtCallsigns = Object(); GT.gtSentAwayToCid = Object(); for (var key in jsmesg.data.calls) { var cid = jsmesg.data.cid[key]; var id = jsmesg.data.id[key]; if (id != myChatId) { if (cid in GT.gtFlagPins) { GT.gtFlagPins[cid].ids[id] = true; } else { GT.gtFlagPins[cid] = Object(); GT.gtFlagPins[cid].ids = Object(); GT.gtFlagPins[cid].ids[id] = true; GT.gtFlagPins[cid].pin = null; } GT.gtFlagPins[cid].call = jsmesg.data.calls[key]; GT.gtFlagPins[cid].fCall = formatCallsign(GT.gtFlagPins[cid].call); GT.gtFlagPins[cid].grid = jsmesg.data.grid[key]; GT.gtFlagPins[cid].freq = jsmesg.data.freq[key]; GT.gtFlagPins[cid].band = jsmesg.data.band[key]; GT.gtFlagPins[cid].mode = jsmesg.data.mode[key]; GT.gtFlagPins[cid].src = jsmesg.data.src[key]; GT.gtFlagPins[cid].cid = cid; GT.gtFlagPins[cid].canmsg = jsmesg.data.canmsg[key]; GT.gtFlagPins[cid].o = jsmesg.data.o[key]; GT.gtFlagPins[cid].dxcc = callsignToDxcc(GT.gtFlagPins[cid].call); GT.gtFlagPins[cid].live = true; if (!(GT.gtFlagPins[cid].call in GT.gtCallsigns)) { GT.gtCallsigns[GT.gtFlagPins[cid].call] = Object(); } GT.gtCallsigns[GT.gtFlagPins[cid].call][cid] = true; makeGtPin(GT.gtFlagPins[cid]); if (GT.gtFlagPins[cid].pin != null) { GT.layerSources.gtflags.addFeature(GT.gtFlagPins[cid].pin); } } } updateChatWindow(); oamsBandActivityCheck(); } function appendToHistory(cid, jsmesg) { if (!(cid in GT.gtMessages)) { GT.gtMessages[cid] = Object(); GT.gtMessages[cid].history = Array(); } GT.gtMessages[cid].history.push(jsmesg); while (GT.gtMessages[cid].history.length > GT.gtMaxChatMessages) { GT.gtMessages[cid].history.shift(); } } function htmlEntities(str) { return String(str) .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """); } function sendSimplePushMessage(jsmesg) { const url = "https://api.simplepush.io/send"; let data = { key: GT.msgSettings.msgSimplepushApiKey, title: "GT Chat - " + formatCallsign(GT.appSettings.myCall), msg: formatCallsign(jsmesg.call) + ": " + jsmesg.msg }; getPostBuffer( url, null, // callback, null, "https", 443, data, 5000 ); } function sendPushOverMessage(jsmesg, test = false) { const url = "https://api.pushover.net/1/messages.json"; let data = { user: GT.msgSettings.msgPushoverUserKey, token: GT.msgSettings.msgPushoverToken, title: "GT Chat - " + formatCallsign(GT.appSettings.myCall), message: formatCallsign(jsmesg.call) + ": " + jsmesg.msg }; getPostBuffer( url, PushoverReply, // callback, test, "https", 443, data, 5000 // timeoutMs, ); } function PushoverReply(data, isTest) { if (isTest) { var result = "Unknown Error"; var color = "#F00"; var responseJson = JSON.parse(data); if (typeof responseJson != "undefined" && typeof responseJson.status != "undefined") { if (responseJson.status == 1) { // {"status":1,"request":"1d5ace84-c2e4-4b19-a051-5ac4c9671170"} color = "#FFF"; result = "Passed!"; } else if (responseJson.status == 0) { color = "#FF0"; if (typeof responseJson.user != "undefined") { // {"user":"invalid","errors":["user identifier is not a valid user, group, or subscribed user key, see https://pushover.net/api#identifiers"],"status":0,"request":"3ef45ac6-38d6-47db-b7aa-7731e8ac0fcb"} result = "User Key Invalid"; } else if (typeof responseJson.token != "undefined") { // {"token":"invalid","errors":["application token is invalid, see https://pushover.net/api"],"status":0,"request":"10629fe4-1e37-4c0f-a123-4585a3fb2aea"} result = "API Token Invalid"; } else { result = "Unknown Response"; } } else { result = "Unknown Status"; } } pushOverTestResultsDiv.innerHTML = result; pushOverTestResultsDiv.style.color = color; } } function gtChatMessage(jsmesg) { if (GT.appSettings.gtMsgEnable == true) { var cid = jsmesg.cid; jsmesg.when = Date.now(); try { jsmesg.msg = new Buffer.from(jsmesg.msg, "base64").toString("utf8"); // eslint-disable-line new-cap jsmesg.msg = htmlEntities(jsmesg.msg); } catch (e) { jsmesg.msg = "Corrupt message received"; } if (jsmesg.call != null && jsmesg.call != "" && jsmesg.call != "NOCALL") { appendToHistory(cid, jsmesg); GT.gtUnread[cid] = true; GT.gtCurrentMessageCount++; if (newChatMessage(cid, jsmesg) == false) { // Only notify if you're not in active chat with them. if (GT.msgSettings.msgSimplepush && GT.msgSettings.msgSimplepushChat && GT.msgSettings.msgSimplepushApiKey != null) { sendSimplePushMessage(jsmesg); } if (GT.msgSettings.msgPushover && GT.msgSettings.msgPushoverChat && GT.msgSettings.msgPushoverUserKey != null && GT.msgSettings.msgPushoverToken != null) { sendPushOverMessage(jsmesg); } alertChatMessage(); } if (GT.msgSettings.msgAwaySelect == 1 && !(cid in GT.gtSentAwayToCid)) { GT.gtSentAwayToCid[cid] = true; gtSendMessage( "Away message [ " + GT.msgSettings.msgAwayText + " ]", cid ); } } } } function gtSendMessage(message, who) { msg = Object(); msg.type = "mesg"; msg.uuid = GT.appSettings.chatUUID; msg.cid = who; msg.msg = new Buffer.from(message).toString("base64"); // eslint-disable-line new-cap sendGtJson(JSON.stringify(msg)); msg.msg = htmlEntities(message); msg.id = 0; msg.when = Date.now(); appendToHistory(who, msg); } function gtChatSendUUID() { var msg = Object(); msg.type = "uuid"; if (GT.appSettings.chatUUID != "") { msg.uuid = GT.appSettings.chatUUID; } else { msg.uuid = null; } msg.call = GT.appSettings.myCall; msg.ver = gtShortVersion; sendGtJson(JSON.stringify(msg), true); GT.gtState = ChatState.waitUUID; } function gtWaitUUID() { console.log("waiting for UUID from OAMS"); } function gtChatSetUUID(jsmesg) { GT.appSettings.chatUUID = jsmesg.uuid; myChatId = jsmesg.id; GT.gtUuidValid = true; gtChatSendStatus(); GT.gtLiveStatusUpdate = false; GT.gtStatusCount = GT.gtStatusTime; GT.gtState = ChatState.status; } GT.getEngineWasRunning = false; function gtChatStateMachine() { if (GT.appSettings.gtShareEnable == true && GT.mapSettings.offlineMode == false) { var now = timeNowSec(); GT.gtStateToFunction[GT.gtState](); if (Object.keys(GT.gtUnread).length > 0 && now % 2 == 0) { msgImg.style.webkitFilter = "invert(1)"; } else msgImg.style.webkitFilter = ""; if (GT.msgSettings.msgFrequencySelect > 0 && Object.keys(GT.gtUnread).length > 0) { if (now - GT.lastChatMsgAlert > GT.msgSettings.msgFrequencySelect * 60) { alertChatMessage(); } } GT.getEngineWasRunning = true; } else { if (GT.getEngineWasRunning == true) { GT.getEngineWasRunning = false; closeGtSocket(); GT.lastGtStatus = ""; } } } function gtSpotMessage(jsmesg) { if (jsmesg.cid in GT.gtFlagPins) { let frequency, band, mode; if (jsmesg.ex != null) { frequency = Number(jsmesg.ex[0]); band = formatBand(Number(frequency / 1000000)); mode = String(jsmesg.ex[1]); } else { frequency = GT.gtFlagPins[jsmesg.cid].freq; band = GT.gtFlagPins[jsmesg.cid].band; mode = GT.gtFlagPins[jsmesg.cid].mode; } addNewOAMSSpot(jsmesg.cid, jsmesg.db, frequency, band, mode); } } function gtChatSystemInit() { GT.gtEngineInterval = nodeTimers.setInterval(gtChatStateMachine, 1000); } function showGtFlags() { if (GT.appSettings.gtFlagImgSrc > 0) { if (GT.mapSettings.offlineMode == false) { redrawPins(); GT.layerVectors.gtflags.setVisible(true); } else { GT.layerVectors.gtflags.setVisible(false); } } else GT.layerVectors.gtflags.setVisible(false); } function clearGtFlags() { GT.layerSources.gtflags.clear(); } function toggleGtMap() { GT.appSettings.gtFlagImgSrc += 1; GT.appSettings.gtFlagImgSrc %= 2; gtFlagImg.src = GT.gtFlagImageArray[GT.appSettings.gtFlagImgSrc]; if (GT.spotView > 0 && GT.receptionSettings.mergeSpots == false) return; if (GT.appSettings.gtFlagImgSrc > 0) { redrawPins(); GT.layerVectors.gtflags.setVisible(true); } else { GT.layerVectors.gtflags.setVisible(false); } } function notifyNoChat(id) { if (GT.chatWindowHandle != null) { try { GT.chatWindowHandle.window.notifyNoChat(id); } catch (e) {} } } function updateChatWindow(id = null) { if (GT.chatWindowHandle != null) { try { if (id) { GT.chatWindowHandle.window.updateCallsign(id); } else { GT.chatWindowHandle.window.updateEverything(); } } catch (e) {} } } function newChatMessage(id, jsmesg) { var hasFocus = false; if (GT.msgSettings.msgActionSelect == 1) showMessaging(); if (GT.chatWindowHandle != null) { try { hasFocus = GT.chatWindowHandle.window.newChatMessage(id, jsmesg); GT.chatWindowHandle.window.messagesRedraw(); } catch (e) {} } return hasFocus; } GT.lastChatMsgAlert = 0; function alertChatMessage() { if (GT.msgSettings.msgAlertSelect == 1) { // Text to speech speakAlertString(GT.msgSettings.msgAlertWord); } if (GT.msgSettings.msgAlertSelect == 2) { // Audible playAlertMediaFile(GT.msgSettings.msgAlertMedia); } GT.lastChatMsgAlert = timeNowSec(); }