diff --git a/package.json b/package.json index 5f19068..17e9ee3 100644 --- a/package.json +++ b/package.json @@ -14,9 +14,10 @@ "chalk": "^5.6.2", "cors": "^2.8.5", "dotenv": "^17.2.3", - "express": "^4.18.2", + "express": "^4.18.2", "http-proxy": "^1.18.1", "jsonwebtoken": "^9.0.2", - "moment": "^2.30.1" + "moment": "^2.30.1", + "ws": "^8.18.3" } } diff --git a/server/handlers.js b/server/handlers.js index 84aff87..5adfd5f 100644 --- a/server/handlers.js +++ b/server/handlers.js @@ -1,6 +1,11 @@ +import { broadcast } from './ws.js'; + const handlers = { updateLocation(req, res) { - console.log("req received") + const { name, latitude, longitude, timestamp } = req.body; + console.log(`Received location: (${name}, ${latitude}, ${longitude}) at ${timestamp}`); + broadcast("update-location", { name, latitude, longitude, timestamp }); + res.json({ success: true }); } } diff --git a/server/index.js b/server/index.js index f1afbbb..eddfc2e 100644 --- a/server/index.js +++ b/server/index.js @@ -4,15 +4,14 @@ import http from 'http' import chalk from 'chalk' import moment from 'moment' import path from 'path'; -import httpProxy from 'http-proxy'; -const proxy = httpProxy.createProxyServer({}); -import { fileURLToPath } from 'url'; +import { initWebSocket } from './ws.js' import Database from "./db.js" import AuthHandler from './auth.js'; import handlers from "./handlers.js"; // Get __dirname in ES6 environment +import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -130,6 +129,7 @@ class Server { app.use('/', router); const server = http.createServer(app); + initWebSocket(server); const PORT = 3008; server.listen(PORT, () => { console.log("\n") diff --git a/server/ws.js b/server/ws.js new file mode 100644 index 0000000..2cf8142 --- /dev/null +++ b/server/ws.js @@ -0,0 +1,31 @@ +import { WebSocket, WebSocketServer } from 'ws'; + +let wss; + +export function initWebSocket(server) { + wss = new WebSocketServer({ server }); + + wss.on('connection', (ws, req) => { + console.log('✅ New WebSocket client connected'); + + ws.on('close', () => { + console.log('Client disconnected'); + }); + }); + + console.log('WebSocket server initialized'); +} + +// Broadcast a message to all connected clients +export function broadcast(reqType, data) { + if (!wss) return; + + const payload = typeof data === 'string' ? data : JSON.stringify(data); + const message = `${reqType}|${payload}`; + + wss.clients.forEach(client => { + if (client.readyState === WebSocket.OPEN) { + client.send(message); + } + }); +} diff --git a/ui/app/components/Home.js b/ui/app/components/Home.js index e2b6494..eac3ae6 100644 --- a/ui/app/components/Home.js +++ b/ui/app/components/Home.js @@ -1,11 +1,19 @@ +import "./LocationList.js" + class Home extends Shadow { render() { ZStack(() => { + p("BLOCKCATCHER") + .x(2, em).y(1, em) + .fontSize(1.2, em) + .fontFamily("Arial") + LocationList() }) .backgroundColor("#aebdff") .display("block") .width(100, vw).height(100, vh) + .color("#60759f") } } diff --git a/ui/app/components/LocationList.js b/ui/app/components/LocationList.js new file mode 100644 index 0000000..bb25460 --- /dev/null +++ b/ui/app/components/LocationList.js @@ -0,0 +1,26 @@ +class LocationList extends Shadow { + list = [] + + render() { + VStack(() => { + this.list.forEach((update) => { + console.log(update.name) + HStack(() => { + p(update.timestamp) + p(update.name) + p(update.latitude) + p(update.longitude) + }) + .gap(1, em) + }) + }) + .onEvent("update-location", (e) => { + console.log("location received: ", e.detail) + this.list.unshift(e.detail) + this.rerender() + }) + .x(2, em).y(5, em) + } +} + +window.registerShadow(LocationList) \ No newline at end of file diff --git a/ui/app/index.js b/ui/app/index.js index b14af88..2174181 100644 --- a/ui/app/index.js +++ b/ui/app/index.js @@ -1,2 +1,5 @@ +import ConnectionHandler from "./ws/Connection.js" +window.ConnectionHandler = new ConnectionHandler() + import "./components/Home.js" Home() \ No newline at end of file diff --git a/ui/app/ws/Connection.js b/ui/app/ws/Connection.js new file mode 100644 index 0000000..1470e89 --- /dev/null +++ b/ui/app/ws/Connection.js @@ -0,0 +1,120 @@ +class Connection { + connectionTries = 0 + ws; + linkCreated; + wsStatus; + + constructor() { + this.init() + } + + init() { + if(window.location.hostname === "localhost") { + this.ws = new WebSocket("ws://" + "localhost:3008") + } else { + this.ws = new WebSocket("wss://" + window.location.hostname + window.location.pathname) + } + this.ws.addEventListener('open', () => { + this.connectionTries = 0 + console.log("Websocket connection established."); + this.ws.addEventListener('message', this.onMessage) + }); + this.ws.addEventListener("close", () => { + this.checkOpen(); + console.log('Websocket Closed') + }) + } + + onMessage = (event) => { + let message = event.data; + const [reqType, payload] = message.split('|'); + + switch(reqType) { + case 'update-location': + window.dispatchEvent(new CustomEvent('update-location', {detail: JSON.parse(payload)})) + break; + default: + console.log('Websocket message:', message); + break; + } + } + + async checkOpen() { + if (this.ws.readyState === WebSocket.OPEN) { + return true + } else { + await this.sleep(this.connectionTries < 20 ? 5000 : 60000) + this.connectionTries++ + console.log('Reestablishing connection') + this.init() + } + } + + sleep = (time) => { + return new Promise(resolve => { + setTimeout(resolve, time); + }); + } + + sendMessage = (msg) => { + if (this.ws.readyState === WebSocket.OPEN) { + this.ws.send(msg); + } + else { + console.log('No websocket connection: Cannot send message'); + } + } + + updateCSS = async (updateStyle, newValue) => { + let currentSpace = window.wrapper.getAttribute("name").replace(/ /g,'%20') + let currentFolder = window.location.pathname; + //create the inital link + this.linkCreated = false; + let cssIncomingText = "" + Array.from(document.styleSheets).forEach(el => { + if(el?.href?.includes?.(`.${currentSpace}.css`)) { + this.linkCreated = true; + + } + }) + let currentSpaceDataAttr = window.wrapper.getAttribute("name"); + let cssInit = `parchment-page[name="${currentSpaceDataAttr}"] {background-color: #e9c9a0;} parchment-page[name="${currentSpaceDataAttr}"] > p.text {font-family: ${newValue};} parchment-page[name="${currentSpaceDataAttr}"] > file-::before, parchment-page[name="${currentSpaceDataAttr}"] > image-::before, parchment-page[name="${currentSpaceDataAttr}"] > parchment-page::before {font-family: ${newValue};font-weight: 400;} parchment-page[name="${currentSpaceDataAttr}"] > space-select-outline > file-::before, parchment-page[name="${currentSpaceDataAttr}"] > select-outline > *, parchment-page[name="${currentSpaceDataAttr}"] > select-outline > image-::before ,parchment-page[name="${currentSpaceDataAttr}"] > space-select-outline > parchment-page::before, parchment-page[name="${currentSpaceDataAttr}"] > a, parchment-page[name="${currentSpaceDataAttr}"] > input-box {font-family: ${newValue}; font-weight: 400;}` + cssIncomingText += cssInit + + let CSSRawData = `REQUEST|update-css|${currentFolder}|${cssIncomingText}|` + await this.checkOpen() + this.ws.send(CSSRawData) + } + + createCSSLink = (wsMessage) => { + let retrieveHref = wsMessage; + var link = document.createElement("link"); + link.rel = "stylesheet"; + link.id = window.wrapper.getAttribute('name')+"-css" + link.href = retrieveHref; + + let retrieveStyleLinks = document.querySelectorAll(`[href='${retrieveHref}']`); + if (retrieveStyleLinks[0] !== undefined) { + retrieveStyleLinks[0].remove(); + } + window.wrapper.prepend(link); + } + + scrapeExternalHTMLTitle = async (href) => { + let req = `REQUEST|scrape-title|${href}|` + await this.checkOpen() + this.ws.send(req) + } + + updateLinkTitle = (title) => { + let link = document.querySelectorAll('.convert-to-title')[0] + if (title !=="") { + link.innerHTML += title; + } else { + link.innerHTML += link.getAttribute('href') + } + link.classList.remove('convert-to-title') + } +} + +export default Connection \ No newline at end of file diff --git a/ui/app/ws/ConnectionHandler.js b/ui/app/ws/ConnectionHandler.js new file mode 100644 index 0000000..a7629b8 --- /dev/null +++ b/ui/app/ws/ConnectionHandler.js @@ -0,0 +1,22 @@ +import Connection from "./Connection.js"; + +export default class ConnectionHandler { + connection; + disabled = true; + + constructor() { + this.connection = new Connection(); + } + + isOpen() { + if(this.connection.checkOpen()) { + return true; + } else { + return false; + } + } + + send(msg) { + this.connection.sendMessage(msg) + } +} \ No newline at end of file