Add some docs

This commit is contained in:
Christopher Cookman 2025-04-19 08:37:50 -06:00
parent 4c53a02d58
commit 453442d92f
6 changed files with 550 additions and 78 deletions

129
index.js
View file

@ -9,12 +9,38 @@ const blacklist = require("./blacklist.json")
const wfos = require("./wfos.json") const wfos = require("./wfos.json")
const events = require("./events.json") const events = require("./events.json")
const os = require('os'); const os = require('os');
const fs = require('fs');
const path = require('path');
const markdown = require('markdown-it')();
const ejs = require('ejs');
const app = express(); const app = express();
expressWs(app); expressWs(app);
// Serve static files from the "public" directory // Serve static files from the "public" directory
app.use(express.static('public')); app.use(express.static('public'));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));
app.get("/", (req, res) => {
const markdownFilePath = path.join(__dirname, 'md', 'DOCS.md');
fs.readFile(markdownFilePath, 'utf8', (err, data) => {
if (err) {
console.error('Error reading markdown file:', err);
res.status(500).send('Internal Server Error');
return;
}
const htmlContent = markdown.render(data);
res.render('docViewer', {title: 'Websocket Docs', htmlContent: htmlContent});
});
});
global.wsConnections = []; global.wsConnections = [];
var roomList = []; var roomList = [];
// IEM WebSocket // IEM WebSocket
@ -36,7 +62,13 @@ app.ws('/iem', (ws, req) => {
try { try {
ws.on('message', (msg) => { ws.on('message', (msg) => {
const data = JSON.parse(msg); const data = JSON.parse(msg);
if (!data.type) return; if (!data.type) return ws.send(JSON.stringify({
"type": "internal-response",
"code": 400,
"data": {
"error": "Invalid request."
}
}));
switch (data.type) { switch (data.type) {
case "subscribe": case "subscribe":
const subscriptionTarget = data.channel || getWFOroom(data.wfo) || "*"; const subscriptionTarget = data.channel || getWFOroom(data.wfo) || "*";
@ -129,6 +161,16 @@ app.ws('/iem', (ws, req) => {
} }
})); }));
} }
break;
default:
ws.send(JSON.stringify({
"type": "internal-response",
"code": 400,
"data": {
"error": "Invalid request."
}
}));
break;
} }
}); });
} catch (error) { } catch (error) {
@ -309,7 +351,7 @@ xmpp.on("offline", () => {
}); });
var restartTimer = null; var restartTimer = null;
var sent = [];
xmpp.on("stanza", (stanza) => { xmpp.on("stanza", (stanza) => {
// Debug stuff // Debug stuff
//if (config.debug >= 2) console.log(`${colors.magenta("[DEBUG]")} Stanza: ${stanza.toString()}`); //if (config.debug >= 2) console.log(`${colors.magenta("[DEBUG]")} Stanza: ${stanza.toString()}`);
@ -373,31 +415,71 @@ xmpp.on("stanza", (stanza) => {
const diff = (now - product_id.timestamp) / 1000 / 60; const diff = (now - product_id.timestamp) / 1000 / 60;
if (diff > 3) return; if (diff > 3) return;
// if (config.debug >= 1) console.log(`${colors.magenta("[DEBUG]")} New message from ${fromChannel}`); // if (config.debug >= 1) console.log(`${colors.magenta("[DEBUG]")} New message from ${fromChannel}`);
console.log(`${colors.cyan("[INFO]")} ${getWFOByRoom(fromChannel).location} - ${evt.text} - ${product_id.timestamp}`); console.log(`${colors.cyan("[INFO]")} ${getWFOByRoom(fromChannel).location} - ${product_id_raw} - ${evt.text} - ${product_id.timestamp}`);
messages++; messages++;
textTries = 0;
// Handle WebSocket tryGetText = () => {
if (wsConnections.length > 0) { fetch(`https://mesonet.agron.iastate.edu/api/1/nwstext/${product_id_raw}`).then((res) => {
wsConnections.forEach((connection) => { // If neither the body nor the product text contains the filter, ignore it
if (connection.subs.includes(fromChannel) || connection.subs.includes("*")) { res.text().then((text) => {
connection.ws.send(JSON.stringify({ if (wsConnections.length > 0) {
"type": "iem-message", wsConnections.forEach((connection) => {
"data": { if (connection.subs.includes(fromChannel) || connection.subs.includes("*")) {
"channel": getWFOByRoom(fromChannel), connection.ws.send(JSON.stringify({
"event": evt, "type": "iem-message",
"body": bodyData.string, "data": {
"timestamp": product_id.timestamp, "channel": getWFOByRoom(fromChannel),
"wmo": product_id.wmo, "event": evt,
"pil": product_id.pil, "body": bodyData.string,
"station": product_id.station, "timestamp": product_id.timestamp,
"raw": product_id_raw, "wmo": product_id.wmo,
"rawBody": body, "pil": product_id.pil,
"image": stanza.getChild("x").attrs.twitter_media || null "station": product_id.station,
"raw": product_id_raw,
"rawBody": body,
"image": stanza.getChild("x").attrs.twitter_media || null,
"productText": text || null
}
}));
}
});
}
});
}).catch((err) => {
setTimeout(() => {
if (textTries >= 3) {
console.log(`${colors.red("[ERROR]")} Failed to fetch product text, giving up... ${err}`)
if (wsConnections.length > 0) {
wsConnections.forEach((connection) => {
if (connection.subs.includes(fromChannel) || connection.subs.includes("*")) {
connection.ws.send(JSON.stringify({
"type": "iem-message",
"data": {
"channel": getWFOByRoom(fromChannel),
"event": evt,
"body": bodyData.string,
"timestamp": product_id.timestamp,
"wmo": product_id.wmo,
"pil": product_id.pil,
"station": product_id.station,
"raw": product_id_raw,
"rawBody": body,
"image": stanza.getChild("x").attrs.twitter_media || null,
"productText": null
}
}));
}
});
} }
})); } else {
} textTries++;
console.log(`${colors.red("[ERROR]")} Failed to fetch product text, retrying... ${err}`)
setTimeout(tryGetText, 100);
}
})
}); });
} }
tryGetText();
} }
}); });
@ -528,3 +610,4 @@ app.listen(PORT, () => {
console.log(`Server is listening on ${PORT}`); console.log(`Server is listening on ${PORT}`);
start(); start();
}); });

187
md/DOCS.md Normal file
View file

@ -0,0 +1,187 @@
# IEM Alerter WebSocket API Documentation
WebSocket Endpoint: `/iem`
This WebSocket endpoint allows clients to subscribe to weather-related channels,
receive real-time updates, and manage their subscriptions.
## Legal Disclaimer
This system is not intended to be used for, or as a supplement for information regarding life or death situations. For critical weather-related information, please rely on official sources such as National Weather Service radio stations, local radio or news outlets, and local emergency management agencies.
## Connection
Upon connecting to the `/iem` WebSocket endpoint, the server will send a connection-info message
containing the following details:
```json
{
"type": "connection-info",
"state": true,
"uuid": "<unique-identifier>",
"host": "<server-hostname>"
}
```
## Supported Message Types
### 1. Subscribe to a Channel
Clients can subscribe to a specific channel or all channels.
**Request:**
```json
{
"type": "subscribe",
"channel": "<channel-name>" // Optional, subscribe to all channels if omitted
}
```
**Response:**
- Success:
```json
{
"type": "internal-response",
"code": 200,
"data": {
"message": "Subscribed to <channel-name>"
}
}
```
- Failure (Invalid Channel):
```json
{
"type": "internal-response",
"code": 404,
"data": {
"error": "Invalid channel."
}
}
```
### 2. Unsubscribe from a Channel
Clients can unsubscribe from a specific channel or all channels.
**Request:**
```json
{
"type": "unsubscribe",
"channel": "<channel-name>" // Optional, unsubscribe from all channels if omitted
}
```
**Response:**
- Success:
```json
{
"type": "internal-response",
"code": 200,
"data": {
"message": "Unsubscribed from <channel-name>"
}
}
```
- Failure (Invalid Channel):
```json
{
"type": "internal-response",
"code": 404,
"data": {
"error": "Invalid channel."
}
}
```
### 3. Get Current Subscriptions
Clients can request a list of their current subscriptions.
**Request:**
```json
{
"type": "get-subscriptions"
}
```
**Response:**
```json
{
"type": "internal-response",
"code": 200,
"data": {
"subscriptions": ["<channel-name-1>", "<channel-name-2>"]
}
}
```
### 4. Get Room List
Clients can request a list of available rooms.
**Request:**
```json
{
"type": "room-list"
}
```
**Response:**
- Success:
```json
{
"type": "room-list",
"code": 200,
"count": <number-of-rooms>,
"data": ["<room-name-1>", "<room-name-2>"]
}
```
- Failure (No Rooms Available):
```json
{
"type": "room-list",
"code": 503,
"count": 0,
"data": {
"error": "Room list is currently empty. Please try again later."
}
}
```
## Real-Time Updates
When subscribed to a channel, clients will receive real-time updates in the following format:
**Message:**
```json
{
"type": "iem-message",
"data": {
"channel": {
"location": "<location-name>",
"room": "<room-name>"
},
"event": {
"name": "<event-name>",
"priority": <priority-level>,
"code": "<event-code>"
},
"body": "<message-body>",
"timestamp": "<timestamp>",
"wmo": "<wmo-code>",
"pil": "<pil-code>",
"station": "<station-name>",
"raw": "<raw-product-id>",
"rawBody": "<raw-message-body>",
"image": "<image-url>", // Optional
"productText": "<product-text>" // Optional
}
}
```
The Priority level is a number from 1 to 5, 1 being the loest priority and 5 being the highest. These priority levels are defined per alert type by the developers of the IEM alerter system.
## Error Handling
If an error occurs, the server will send an error response:
```json
{
"type": "internal-response",
"code": 500,
"data": {
"error": "Internal server error."
}
}
```

208
package-lock.json generated
View file

@ -12,9 +12,11 @@
"@xmpp/client": "^0.13.4", "@xmpp/client": "^0.13.4",
"colors": "^1.4.0", "colors": "^1.4.0",
"dotenv": "^16.5.0", "dotenv": "^16.5.0",
"ejs": "^3.1.10",
"express": "^5.1.0", "express": "^5.1.0",
"express-ws": "^5.0.2", "express-ws": "^5.0.2",
"html-entities": "^2.6.0" "html-entities": "^2.6.0",
"markdown-it": "^14.1.0"
} }
}, },
"node_modules/@ampproject/remapping": { "node_modules/@ampproject/remapping": {
@ -782,6 +784,21 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/anymatch": { "node_modules/anymatch": {
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
@ -796,6 +813,12 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"license": "Python-2.0"
},
"node_modules/array-buffer-byte-length": { "node_modules/array-buffer-byte-length": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz",
@ -855,6 +878,12 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/async": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
"integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
"license": "MIT"
},
"node_modules/async-function": { "node_modules/async-function": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz",
@ -1091,6 +1120,22 @@
], ],
"license": "CC-BY-4.0" "license": "CC-BY-4.0"
}, },
"node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/chokidar": { "node_modules/chokidar": {
"version": "3.6.0", "version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
@ -1143,6 +1188,24 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"license": "MIT",
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"license": "MIT"
},
"node_modules/colors": { "node_modules/colors": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
@ -1408,6 +1471,21 @@
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/ejs": {
"version": "3.1.10",
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
"integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==",
"license": "Apache-2.0",
"dependencies": {
"jake": "^10.8.5"
},
"bin": {
"ejs": "bin/cli.js"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.5.136", "version": "1.5.136",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.136.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.136.tgz",
@ -1423,6 +1501,18 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/es-abstract": { "node_modules/es-abstract": {
"version": "1.23.9", "version": "1.23.9",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz",
@ -1690,6 +1780,36 @@
"node": "^12.20 || >= 14.13" "node": "^12.20 || >= 14.13"
} }
}, },
"node_modules/filelist": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz",
"integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==",
"license": "Apache-2.0",
"dependencies": {
"minimatch": "^5.0.1"
}
},
"node_modules/filelist/node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/filelist/node_modules/minimatch": {
"version": "5.1.6",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
"integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=10"
}
},
"node_modules/fill-range": { "node_modules/fill-range": {
"version": "7.1.1", "version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
@ -2001,6 +2121,15 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/has-property-descriptors": { "node_modules/has-property-descriptors": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
@ -2565,6 +2694,24 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/jake": {
"version": "10.9.2",
"resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz",
"integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==",
"license": "Apache-2.0",
"dependencies": {
"async": "^3.2.3",
"chalk": "^4.0.2",
"filelist": "^1.0.4",
"minimatch": "^3.1.2"
},
"bin": {
"jake": "bin/cli.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/js-tokens": { "node_modules/js-tokens": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@ -2610,6 +2757,15 @@
"integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==", "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/linkify-it": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
"integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
"license": "MIT",
"dependencies": {
"uc.micro": "^2.0.0"
}
},
"node_modules/locate-path": { "node_modules/locate-path": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
@ -2663,6 +2819,23 @@
"semver": "bin/semver" "semver": "bin/semver"
} }
}, },
"node_modules/markdown-it": {
"version": "14.1.0",
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz",
"integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==",
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1",
"entities": "^4.4.0",
"linkify-it": "^5.0.0",
"mdurl": "^2.0.0",
"punycode.js": "^2.3.1",
"uc.micro": "^2.1.0"
},
"bin": {
"markdown-it": "bin/markdown-it.mjs"
}
},
"node_modules/math-intrinsics": { "node_modules/math-intrinsics": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@ -2683,6 +2856,12 @@
"safe-buffer": "^5.1.2" "safe-buffer": "^5.1.2"
} }
}, },
"node_modules/mdurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
"integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
"license": "MIT"
},
"node_modules/media-typer": { "node_modules/media-typer": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
@ -3089,6 +3268,15 @@
"node": ">= 0.10" "node": ">= 0.10"
} }
}, },
"node_modules/punycode.js": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
"integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/qs": { "node_modules/qs": {
"version": "6.14.0", "version": "6.14.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
@ -3648,6 +3836,18 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"license": "MIT",
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/to-regex-range": { "node_modules/to-regex-range": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@ -3758,6 +3958,12 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/uc.micro": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
"integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
"license": "MIT"
},
"node_modules/unbox-primitive": { "node_modules/unbox-primitive": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz",

View file

@ -13,8 +13,10 @@
"@xmpp/client": "^0.13.4", "@xmpp/client": "^0.13.4",
"colors": "^1.4.0", "colors": "^1.4.0",
"dotenv": "^16.5.0", "dotenv": "^16.5.0",
"ejs": "^3.1.10",
"express": "^5.1.0", "express": "^5.1.0",
"express-ws": "^5.0.2", "express-ws": "^5.0.2",
"html-entities": "^2.6.0" "html-entities": "^2.6.0",
"markdown-it": "^14.1.0"
} }
} }

View file

@ -1,52 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="stuff"></div>
<script>
const socket = new WebSocket(`ws://${window.location.host}/iem`);
socket.onopen = () => {
const newDiv = document.createElement('div');
newDiv.innerText = "WebSocket connection opened";
document.getElementById('stuff').prepend(newDiv);
};
socket.onclose = () => {
const newDiv = document.createElement('div');
newDiv.innerText = "WebSocket connection closed";
document.getElementById('stuff').prepend(newDiv);
};
socket.onmessage = (event) => {
const data = JSON.parse(event.data);
if (!data.type) return;
switch (data.type) {
case "iem-message":
const explanation = `Event: ${JSON.stringify(data.event)}, Channel: ${JSON.stringify(data.channel)}, Station: ${data.station}`;
console.log(explanation);
const newDiv = document.createElement('div');
newDiv.innerText = explanation;
document.getElementById('stuff').prepend(newDiv);
break;
case "internal-response":
const messageBox = document.createElement('textarea');
messageBox.value = data.data.message;
messageBox.style.width = "100%";
messageBox.style.height = "100px";
document.getElementById('stuff').prepend(messageBox);
break;
}
};
socket.send(JSON.stringify({ type: "subscribe", channel: "*" }));
</script>
</body>
</html>

46
views/docViewer.ejs Normal file
View file

@ -0,0 +1,46 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= title %></title>
<style>
body {
font-family: Arial, sans-serif;
line-height: 1.6;
margin: 20px;
padding: 0;
background-color: #121212;
color: #e0e0e0;
}
h1, h2, h3, h4, h5, h6 {
color: #ffffff;
}
a {
color: #1e90ff;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
pre {
background: #1e1e1e;
padding: 10px;
border-radius: 5px;
overflow-x: auto;
color: #dcdcdc;
}
code {
background: #1e1e1e;
padding: 2px 4px;
border-radius: 3px;
color: #dcdcdc;
}
</style>
</head>
<body>
<div class="markdown-content">
<%- htmlContent %>
</div>
</body>
</html>