gridtracker/package.nw/lib/gtws.js

921 wiersze
20 KiB
JavaScript

// GridTracker Copyright © 2024 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, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;");
}
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();
}