Ability to post in Forum
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
"HY-1": {
|
"HY-1": {
|
||||||
"fullName": "Founder"
|
"fullName": "Founder"
|
||||||
},
|
},
|
||||||
"MEMBER-samrussell99@pm.me": {
|
"MEMBER-1": {
|
||||||
"email": "samrussell99@pm.me",
|
"email": "samrussell99@pm.me",
|
||||||
"firstName": "Sam",
|
"firstName": "Sam",
|
||||||
"lastName": "Russell",
|
"lastName": "Russell",
|
||||||
@@ -317,6 +317,11 @@
|
|||||||
"url": "https://hyperia.so/signup?token=",
|
"url": "https://hyperia.so/signup?token=",
|
||||||
"uuid": "f2016b40-a179-47ec-a415-84caa7da8521",
|
"uuid": "f2016b40-a179-47ec-a415-84caa7da8521",
|
||||||
"used": false
|
"used": false
|
||||||
|
},
|
||||||
|
"POST-HY-1": {
|
||||||
|
"text": "This is the first message ever.",
|
||||||
|
"time": "11.24.2025-12:54:360784am",
|
||||||
|
"sentBy": "MEMBER-1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"edges": {}
|
"edges": {}
|
||||||
|
|||||||
2
main.js
2
main.js
@@ -2,6 +2,8 @@
|
|||||||
const { app, BrowserWindow, nativeImage } = require('electron');
|
const { app, BrowserWindow, nativeImage } = require('electron');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
|
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = "true";
|
||||||
|
|
||||||
function createWindow() {
|
function createWindow() {
|
||||||
const win = new BrowserWindow({
|
const win = new BrowserWindow({
|
||||||
show: false, // window is hidden
|
show: false, // window is hidden
|
||||||
|
|||||||
@@ -28,6 +28,21 @@ export default class AuthHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getProfile(req, res) {
|
||||||
|
const token = req.cookies.auth_token;
|
||||||
|
if (!token) return res.status(401).send({ error: "No auth token" });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const payload = jwt.verify(token, process.env.JWT_SECRET);
|
||||||
|
const email = payload.email;
|
||||||
|
|
||||||
|
const user = db.members.getByEmail(email);
|
||||||
|
res.send({ email: user.email, name: user.firstName + " " + user.lastName });
|
||||||
|
} catch (err) {
|
||||||
|
res.status(401).send({ error: "Invalid token" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async login(req, res) {
|
async login(req, res) {
|
||||||
const { email, password } = req.body;
|
const { email, password } = req.body;
|
||||||
let foundUser = global.db.members.getByEmail(email)
|
let foundUser = global.db.members.getByEmail(email)
|
||||||
@@ -40,7 +55,8 @@ export default class AuthHandler {
|
|||||||
if (!valid) {
|
if (!valid) {
|
||||||
res.status(400).json({ error: 'Incorrect password.' });
|
res.status(400).json({ error: 'Incorrect password.' });
|
||||||
} else {
|
} else {
|
||||||
const payload = { id: foundUser.id };
|
const payload = { email: foundUser.email };
|
||||||
|
console.log(payload)
|
||||||
const secret = process.env.JWT_SECRET;
|
const secret = process.env.JWT_SECRET;
|
||||||
const options = { expiresIn: "2h" };
|
const options = { expiresIn: "2h" };
|
||||||
const token = jwt.sign(payload, secret, options);
|
const token = jwt.sign(payload, secret, options);
|
||||||
|
|||||||
@@ -5,16 +5,19 @@ import QuillDB from "../_/quilldb.js"
|
|||||||
import Titles from "./model/Titles.js"
|
import Titles from "./model/Titles.js"
|
||||||
import Members from './model/Members.js'
|
import Members from './model/Members.js'
|
||||||
import Tokens from './model/Tokens.js'
|
import Tokens from './model/Tokens.js'
|
||||||
|
import Posts from "./model/Posts.js"
|
||||||
|
|
||||||
export default class Database {
|
export default class Database {
|
||||||
titles = new Titles()
|
titles = new Titles()
|
||||||
members = new Members()
|
members = new Members()
|
||||||
tokens = new Tokens()
|
tokens = new Tokens()
|
||||||
|
posts = new Posts()
|
||||||
|
|
||||||
fromID = {
|
fromID = {
|
||||||
"HY": this.titles,
|
"HY": this.titles,
|
||||||
"MEMBER": this.members,
|
"MEMBER": this.members,
|
||||||
"TOKEN": this.tokens
|
"TOKEN": this.tokens,
|
||||||
|
"POST": this.posts
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -39,7 +42,7 @@ export default class Database {
|
|||||||
try {
|
try {
|
||||||
let collection = this.fromID[type]
|
let collection = this.fromID[type]
|
||||||
if(collection) {
|
if(collection) {
|
||||||
collection.save(node)
|
collection.save(node, id)
|
||||||
} else {
|
} else {
|
||||||
throw new Error("Type does not exist for node: ", id)
|
throw new Error("Type does not exist for node: ", id)
|
||||||
}
|
}
|
||||||
@@ -63,12 +66,14 @@ export default class Database {
|
|||||||
let arrs = [
|
let arrs = [
|
||||||
this.titles.entries,
|
this.titles.entries,
|
||||||
this.members.entries,
|
this.members.entries,
|
||||||
this.tokens.entries
|
this.tokens.entries,
|
||||||
|
this.posts.entries
|
||||||
]
|
]
|
||||||
let ids = [
|
let ids = [
|
||||||
Object.entries(this.titles.ids),
|
Object.entries(this.titles.ids),
|
||||||
Object.entries(this.members.ids),
|
Object.entries(this.members.ids),
|
||||||
Object.entries(this.tokens.ids),
|
Object.entries(this.tokens.ids),
|
||||||
|
Object.entries(this.posts.ids),
|
||||||
]
|
]
|
||||||
for(let i=0; i<arrs.length; i++) {
|
for(let i=0; i<arrs.length; i++) {
|
||||||
let arr = arrs[i]
|
let arr = arrs[i]
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export default class Members extends OrderedObject {
|
|||||||
isHashed = (s) => {return s.startsWith("$argon2")}
|
isHashed = (s) => {return s.startsWith("$argon2")}
|
||||||
|
|
||||||
save(member) {
|
save(member) {
|
||||||
let id = `MEMBER-${member.email}`
|
let id = `MEMBER-${this.entries.length+1}`
|
||||||
let result = this.schema.safeParse(member)
|
let result = this.schema.safeParse(member)
|
||||||
if(result.success) {
|
if(result.success) {
|
||||||
try {
|
try {
|
||||||
@@ -45,29 +45,31 @@ export default class Members extends OrderedObject {
|
|||||||
newMember.tokenUsed = tokenID
|
newMember.tokenUsed = tokenID
|
||||||
const hash = await argon2.hash(newMember.password);
|
const hash = await argon2.hash(newMember.password);
|
||||||
newMember.password = hash
|
newMember.password = hash
|
||||||
newMember.joined = this.currentTime()
|
newMember.joined = global.currentTime()
|
||||||
this.save(newMember)
|
this.save(newMember)
|
||||||
}
|
}
|
||||||
|
|
||||||
getByEmail(email) {
|
get(id) {
|
||||||
return super.get(`MEMBER-${email}`)
|
return this.entries[this.ids[id]]
|
||||||
}
|
}
|
||||||
|
|
||||||
currentTime() {
|
getByEmail(email) {
|
||||||
const now = new Date();
|
for(let i=0; i<this.entries.length; i++) {
|
||||||
|
if(this.entries[i].email === email) {
|
||||||
|
return this.entries[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
const month = String(now.getMonth() + 1).padStart(2, "0");
|
getIDFromEmail(email) {
|
||||||
const day = String(now.getDate()).padStart(2, "0");
|
let index = 0
|
||||||
const year = now.getFullYear();
|
for(let i=0; i<this.entries.length; i++) {
|
||||||
|
if(this.entries[i].email === email) {
|
||||||
let hours = now.getHours();
|
index = i
|
||||||
const ampm = hours >= 12 ? "pm" : "am";
|
break
|
||||||
hours = hours % 12 || 12; // convert to 12-hour format
|
}
|
||||||
|
}
|
||||||
const minutes = String(now.getMinutes()).padStart(2, "0");
|
return Object.entries(this.ids)[index][0]
|
||||||
const seconds = String(now.getSeconds()).padStart(2, "0");
|
|
||||||
const ms = String(now.getMilliseconds()).padStart(4, "0"); // 4-digit like "5838"
|
|
||||||
|
|
||||||
return `${month}.${day}.${year}-${hours}:${minutes}:${seconds}${ms}${ampm}`;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
export default class OrderedObject {
|
export default class OrderedObject {
|
||||||
entries = []
|
entries = []
|
||||||
ids = {}
|
ids = {}
|
||||||
|
indexes = []
|
||||||
|
|
||||||
add(id, data) {
|
add(id, data) {
|
||||||
if(this.get(id)) {
|
if(this.ids[id]) {
|
||||||
console.error(`Can't add item ${id}: id already exists`)
|
console.error(`Can't add item ${id}: id already exists`)
|
||||||
throw new global.ServerError(400, `Member with this email already exists`)
|
throw new global.ServerError(400, `Member with this email already exists`)
|
||||||
}
|
}
|
||||||
@@ -23,12 +24,4 @@ export default class OrderedObject {
|
|||||||
return this.entries[this.ids[key]]
|
return this.entries[this.ids[key]]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get(key) {
|
|
||||||
if (typeof key === "number") {
|
|
||||||
return this.entries[key]
|
|
||||||
} else {
|
|
||||||
return this.entries[this.ids[key]]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
56
server/db/model/Posts.js
Normal file
56
server/db/model/Posts.js
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import OrderedObject from "./OrderedObject.js"
|
||||||
|
const { z } = require("zod")
|
||||||
|
|
||||||
|
export default class Posts extends OrderedObject {
|
||||||
|
schema = z.object({
|
||||||
|
text: z.string(),
|
||||||
|
time: z.string(),
|
||||||
|
sentBy: z.string()
|
||||||
|
})
|
||||||
|
|
||||||
|
makeID(forum, number) {
|
||||||
|
return `POST-${forum}-${number}`
|
||||||
|
}
|
||||||
|
|
||||||
|
save(post, id) {
|
||||||
|
let result = this.schema.safeParse(post)
|
||||||
|
if(result.success) {
|
||||||
|
try {
|
||||||
|
super.add(id, post)
|
||||||
|
} catch(e) {
|
||||||
|
console.error(e)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error("Failed parsing member: ", result.error)
|
||||||
|
throw new global.ServerError(400, "Invalid Member Data!: ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get(forum, number) {
|
||||||
|
let result = []
|
||||||
|
let limit = Math.min(number, this.entries.length)
|
||||||
|
for(let i=1; i<=limit; i++) {
|
||||||
|
let id = this.makeID(forum, i)
|
||||||
|
let post = this.entries[this.ids[id]]
|
||||||
|
let {firstName, lastName} = global.db.members.get(post.sentBy)
|
||||||
|
let seededObj = {
|
||||||
|
...post
|
||||||
|
}
|
||||||
|
seededObj.sentByID = post.sentBy
|
||||||
|
seededObj.sentBy = firstName + " " + lastName
|
||||||
|
result.push(seededObj)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
async add(text, forum, userEmail) {
|
||||||
|
let newPost = {}
|
||||||
|
newPost.text = text
|
||||||
|
newPost.sentBy = db.members.getIDFromEmail(userEmail)
|
||||||
|
newPost.time = global.currentTime()
|
||||||
|
|
||||||
|
let idNumber = this.entries.length+1
|
||||||
|
super.add(this.makeID(forum, idNumber), newPost)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,6 @@ export default class Titles extends OrderedObject {
|
|||||||
|
|
||||||
save(newTitle) {
|
save(newTitle) {
|
||||||
let id = `HY-${this.entries.length+1}`
|
let id = `HY-${this.entries.length+1}`
|
||||||
console.log(id)
|
|
||||||
if(this.validate(id, newTitle)) {
|
if(this.validate(id, newTitle)) {
|
||||||
try {
|
try {
|
||||||
super.add(id, newTitle)
|
super.add(id, newTitle)
|
||||||
|
|||||||
@@ -36,6 +36,6 @@ export default class Tokens extends OrderedObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get(uuid) {
|
get(uuid) {
|
||||||
return super.get(`TOKEN-${uuid}`)
|
return this.entries[this.ids[`TOKEN-${uuid}`]]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import { broadcast } from './ws.js';
|
|
||||||
|
|
||||||
const handlers = {
|
|
||||||
updateLocation(req, res) {
|
|
||||||
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 });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default handlers;
|
|
||||||
@@ -7,10 +7,10 @@ const chalk = require('chalk');
|
|||||||
const moment = require('moment');
|
const moment = require('moment');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
import { initWebSocket } from './ws.js'
|
import "./util.js"
|
||||||
|
import Socket from './ws/ws.js'
|
||||||
import Database from "./db/db.js"
|
import Database from "./db/db.js"
|
||||||
import AuthHandler from './auth.js';
|
import AuthHandler from './auth.js';
|
||||||
import handlers from "./handlers.js";
|
|
||||||
|
|
||||||
class Server {
|
class Server {
|
||||||
db;
|
db;
|
||||||
@@ -21,6 +21,7 @@ class Server {
|
|||||||
registerRoutes(router) {
|
registerRoutes(router) {
|
||||||
// router.post('/api/location', handlers.updateLocation)
|
// router.post('/api/location', handlers.updateLocation)
|
||||||
router.post('/login', this.auth.login)
|
router.post('/login', this.auth.login)
|
||||||
|
router.get('/profile', this.auth.getProfile)
|
||||||
router.get('/signout', this.auth.logout)
|
router.get('/signout', this.auth.logout)
|
||||||
router.get('/signup', this.verifyToken, this.get)
|
router.get('/signup', this.verifyToken, this.get)
|
||||||
router.post('/signup', this.verifyToken, this.newUserSubmission)
|
router.post('/signup', this.verifyToken, this.newUserSubmission)
|
||||||
@@ -171,8 +172,13 @@ class Server {
|
|||||||
this.registerRoutes(router)
|
this.registerRoutes(router)
|
||||||
app.use('/', router);
|
app.use('/', router);
|
||||||
|
|
||||||
|
setInterval(() => {
|
||||||
|
console.log("saving db")
|
||||||
|
global.db.saveData()
|
||||||
|
}, 5000)
|
||||||
|
|
||||||
const server = http.createServer(app);
|
const server = http.createServer(app);
|
||||||
initWebSocket(server);
|
global.Socket = new Socket(server);
|
||||||
const PORT = 3003;
|
const PORT = 3003;
|
||||||
server.listen(PORT, () => {
|
server.listen(PORT, () => {
|
||||||
console.logclean("\n")
|
console.logclean("\n")
|
||||||
@@ -219,11 +225,4 @@ console.logclean = function (...args) {
|
|||||||
// _log.call(console, `[${location}]`, ...args);
|
// _log.call(console, `[${location}]`, ...args);
|
||||||
// };
|
// };
|
||||||
|
|
||||||
global.ServerError = class extends Error {
|
|
||||||
constructor(status, msg) {
|
|
||||||
super(msg);
|
|
||||||
this.status = status;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const server = new Server()
|
const server = new Server()
|
||||||
24
server/util.js
Normal file
24
server/util.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
global.ServerError = class extends Error {
|
||||||
|
constructor(status, msg) {
|
||||||
|
super(msg);
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
global.currentTime = function () {
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
const month = String(now.getMonth() + 1).padStart(2, "0");
|
||||||
|
const day = String(now.getDate()).padStart(2, "0");
|
||||||
|
const year = now.getFullYear();
|
||||||
|
|
||||||
|
let hours = now.getHours();
|
||||||
|
const ampm = hours >= 12 ? "pm" : "am";
|
||||||
|
hours = hours % 12 || 12; // convert to 12-hour format
|
||||||
|
|
||||||
|
const minutes = String(now.getMinutes()).padStart(2, "0");
|
||||||
|
const seconds = String(now.getSeconds()).padStart(2, "0");
|
||||||
|
const ms = String(now.getMilliseconds()).padStart(4, "0"); // 4-digit like "5838"
|
||||||
|
|
||||||
|
return `${month}.${day}.${year}-${hours}:${minutes}:${seconds}${ms}${ampm}`;
|
||||||
|
}
|
||||||
31
server/ws.js
31
server/ws.js
@@ -1,31 +0,0 @@
|
|||||||
const { WebSocket, WebSocketServer } = require('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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
43
server/ws/handlers/ForumHandler.js
Normal file
43
server/ws/handlers/ForumHandler.js
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
const { z } = require("zod")
|
||||||
|
|
||||||
|
const sendSchema = z.object({
|
||||||
|
forum: z.string(),
|
||||||
|
text: z.string(),
|
||||||
|
})
|
||||||
|
.strict()
|
||||||
|
|
||||||
|
const getSchema = z.object({
|
||||||
|
forum: z.string(),
|
||||||
|
number: z.number()
|
||||||
|
})
|
||||||
|
.strict()
|
||||||
|
|
||||||
|
|
||||||
|
export default class ForumHandler {
|
||||||
|
static handleSend(msg, ws) {
|
||||||
|
try {
|
||||||
|
global.db.posts.add(msg.text, msg.forum, ws.userEmail)
|
||||||
|
global.Socket.broadcast({event: "new-message", app: "FORUM", forum: msg.forum, msg: this.handleGet({forum: msg.forum, number: 100})})
|
||||||
|
return {success: true}
|
||||||
|
} catch(e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static handleGet(msg) {
|
||||||
|
let data = global.db.posts.get(msg.forum, msg.number)
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
static handle(operation, msg, ws) {
|
||||||
|
switch(operation) {
|
||||||
|
case "SEND":
|
||||||
|
if(!sendSchema.safeParse(msg).success) throw new Error("Incorrectly formatted Forum ws message!")
|
||||||
|
return this.handleSend(msg, ws)
|
||||||
|
case "GET":
|
||||||
|
if(!getSchema.safeParse(msg).success) throw new Error("Incorrectly formatted Forum ws message!")
|
||||||
|
return this.handleGet(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
93
server/ws/ws.js
Normal file
93
server/ws/ws.js
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
const { WebSocket, WebSocketServer } = require('ws');
|
||||||
|
const { z } = require("zod")
|
||||||
|
const jwt = require('jsonwebtoken');
|
||||||
|
import ForumHandler from "./handlers/ForumHandler.js"
|
||||||
|
|
||||||
|
export default class Socket {
|
||||||
|
wss;
|
||||||
|
messageSchema = z.object({
|
||||||
|
id: z.string(),
|
||||||
|
app: z.string(),
|
||||||
|
operation: z.string().optional(),
|
||||||
|
msg: z.union([
|
||||||
|
z.object({}).passthrough(), // allows any object
|
||||||
|
z.array(z.any()) // allows any array
|
||||||
|
])
|
||||||
|
}).strict()
|
||||||
|
|
||||||
|
constructor(server) {
|
||||||
|
this.wss = new WebSocketServer({ server });
|
||||||
|
|
||||||
|
this.wss.on('connection', (ws, req) => {
|
||||||
|
console.log('✅ New WebSocket client connected');
|
||||||
|
|
||||||
|
function parseCookies(cookieHeader = "") {
|
||||||
|
return Object.fromEntries(
|
||||||
|
cookieHeader.split(";").map(c => {
|
||||||
|
const [key, ...v] = c.trim().split("=");
|
||||||
|
return [key, decodeURIComponent(v.join("="))];
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const cookies = parseCookies(req.headers.cookie);
|
||||||
|
const token = cookies.auth_token;
|
||||||
|
if (!token) throw new Error("No auth token");
|
||||||
|
const payload = jwt.verify(token, process.env.JWT_SECRET);
|
||||||
|
ws.userEmail = payload.email;
|
||||||
|
|
||||||
|
ws.on('message', (msg) => {
|
||||||
|
this.handleMessage(msg, ws);
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.on('close', () => {
|
||||||
|
console.log('Client disconnected');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('WebSocket server initialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build a system where the ws obj is updated every time on navigate, so it already has context
|
||||||
|
// this way, we can only send broadcast messages to clients that actually have that app / subapp open
|
||||||
|
handleMessage = (msg, ws) => {
|
||||||
|
try {
|
||||||
|
const text = msg.toString();
|
||||||
|
const req = JSON.parse(text);
|
||||||
|
if(!this.messageSchema.safeParse(req).success) throw new Error("Socket.handleMessage: Incorrectly formatted incoming ws message!")
|
||||||
|
|
||||||
|
let responseData;
|
||||||
|
switch (req.app) {
|
||||||
|
case "FORUM":
|
||||||
|
responseData = ForumHandler.handle(req.operation, req.msg, ws)
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
console.error("unknown ws message")
|
||||||
|
}
|
||||||
|
|
||||||
|
let response = {
|
||||||
|
...req
|
||||||
|
}
|
||||||
|
response.msg = responseData
|
||||||
|
|
||||||
|
if(!this.messageSchema.safeParse(response).success) throw new Error("Socket.handleMessage: Incorrectly formatted outgoing ws message!")
|
||||||
|
ws.send(JSON.stringify(response))
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Invalid WS message:", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
broadcast(event) {
|
||||||
|
if (!this.wss) return;
|
||||||
|
|
||||||
|
let message = JSON.stringify(event)
|
||||||
|
|
||||||
|
this.wss.clients.forEach(ws => {
|
||||||
|
if (ws.readyState === WebSocket.OPEN) {
|
||||||
|
ws.send(message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
Sam Russell
|
Sam Russell
|
||||||
Captured Sun
|
Captured Sun
|
||||||
|
11.24.25 - Fixing onClick because it was reversed, adding event to onHover params
|
||||||
11.23.25 - Added onSubmit() event for form submission, added marginHorizontal() and marginVertical()
|
11.23.25 - Added onSubmit() event for form submission, added marginHorizontal() and marginVertical()
|
||||||
11.20.25 - Added "pct" style unit, added alignVertical and alignHorizontal for flex boxes
|
11.20.25 - Added "pct" style unit, added alignVertical and alignHorizontal for flex boxes
|
||||||
11.19.25 - Allowing for "auto" values in otherwise numeric styles, adding vmin and vmax units
|
11.19.25 - Allowing for "auto" values in otherwise numeric styles, adding vmin and vmax units
|
||||||
@@ -496,7 +497,6 @@ HTMLElement.prototype.alignVertical = function (value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (direction === "column" || direction === "column-reverse") {
|
if (direction === "column" || direction === "column-reverse") {
|
||||||
console.log("using justify")
|
|
||||||
this.style.justifyContent = value;
|
this.style.justifyContent = value;
|
||||||
} else {
|
} else {
|
||||||
this.style.alignItems = value;
|
this.style.alignItems = value;
|
||||||
@@ -824,8 +824,8 @@ HTMLElement.prototype.onAppear = function(func) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
HTMLElement.prototype.onClick = function(func) {
|
HTMLElement.prototype.onClick = function(func) {
|
||||||
const onMouseDown = () => func.call(this, true);
|
const onMouseDown = () => func.call(this, false);
|
||||||
const onMouseUp = () => func.call(this, false);
|
const onMouseUp = () => func.call(this, true);
|
||||||
this._storeListener("mousedown", onMouseDown);
|
this._storeListener("mousedown", onMouseDown);
|
||||||
this._storeListener("mouseup", onMouseUp);
|
this._storeListener("mouseup", onMouseUp);
|
||||||
return this;
|
return this;
|
||||||
@@ -847,8 +847,8 @@ HTMLElement.prototype.onRightClick = function(func) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
HTMLElement.prototype.onHover = function(cb) {
|
HTMLElement.prototype.onHover = function(cb) {
|
||||||
const onEnter = () => cb.call(this, true);
|
const onEnter = (e) => cb.call(this, true, e);
|
||||||
const onLeave = () => cb.call(this, false);
|
const onLeave = (e) => cb.call(this, false, e);
|
||||||
this._storeListener("mouseover", onEnter);
|
this._storeListener("mouseover", onEnter);
|
||||||
this._storeListener("mouseleave", onLeave);
|
this._storeListener("mouseleave", onLeave);
|
||||||
return this;
|
return this;
|
||||||
|
|||||||
8
ui/_/code/zod.js
Normal file
8
ui/_/code/zod.js
Normal file
File diff suppressed because one or more lines are too long
@@ -1,3 +1,5 @@
|
|||||||
|
import './MessagesPanel.js'
|
||||||
|
|
||||||
css(`
|
css(`
|
||||||
forum- {
|
forum- {
|
||||||
font-family: 'Bona';
|
font-family: 'Bona';
|
||||||
@@ -23,8 +25,6 @@ css(`
|
|||||||
`)
|
`)
|
||||||
|
|
||||||
class Forum extends Shadow {
|
class Forum extends Shadow {
|
||||||
friends = []
|
|
||||||
conversations = []
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
ZStack(() => {
|
ZStack(() => {
|
||||||
@@ -46,13 +46,25 @@ class Forum extends Shadow {
|
|||||||
.gap(0, em)
|
.gap(0, em)
|
||||||
|
|
||||||
VStack(() => {
|
VStack(() => {
|
||||||
input("Message Hyperia", "80%")
|
|
||||||
|
MessagesPanel()
|
||||||
|
|
||||||
|
input("Message Hyperia", "93%")
|
||||||
.paddingVertical(1, em)
|
.paddingVertical(1, em)
|
||||||
|
.paddingHorizontal(2, em)
|
||||||
.color("var(--accent)")
|
.color("var(--accent)")
|
||||||
.background("var(--darkbrown)")
|
.background("var(--darkbrown)")
|
||||||
.marginBottom(6, em)
|
.marginBottom(6, em)
|
||||||
.border("none")
|
.border("none")
|
||||||
|
.fontSize(1, em)
|
||||||
|
.onKeyDown(function (e) {
|
||||||
|
if (e.key === "Enter") {
|
||||||
|
window.Socket.send({app: "FORUM", operation: "SEND", msg: {forum: "HY", text: this.value }})
|
||||||
|
this.value = ""
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
.gap(1, em)
|
||||||
.width(100, pct)
|
.width(100, pct)
|
||||||
.alignHorizontal("center")
|
.alignHorizontal("center")
|
||||||
.alignVertical("end")
|
.alignVertical("end")
|
||||||
@@ -120,6 +132,8 @@ class Forum extends Shadow {
|
|||||||
.width(100, pct)
|
.width(100, pct)
|
||||||
.height(100, pct)
|
.height(100, pct)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
register(Forum)
|
register(Forum)
|
||||||
83
ui/site/apps/Forum/MessagesPanel.js
Normal file
83
ui/site/apps/Forum/MessagesPanel.js
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
class MessagesPanel extends Shadow {
|
||||||
|
forums = [
|
||||||
|
"HY"
|
||||||
|
]
|
||||||
|
messages = []
|
||||||
|
|
||||||
|
render() {
|
||||||
|
VStack(() => {
|
||||||
|
if(this.messages.length > 0) {
|
||||||
|
for(let i=0; i<this.messages.length; i++) {
|
||||||
|
let message = this.messages[i]
|
||||||
|
VStack(() => {
|
||||||
|
HStack(() => {
|
||||||
|
p(message.sentBy)
|
||||||
|
.fontWeight("bold")
|
||||||
|
.marginBottom(0.3, em)
|
||||||
|
|
||||||
|
p(this.formatTime(message.time))
|
||||||
|
.opacity(0.2)
|
||||||
|
.marginLeft(1, em)
|
||||||
|
})
|
||||||
|
p(message.text)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
div()
|
||||||
|
.borderRadius(100, pct)
|
||||||
|
.width(2, em).height(2, em)
|
||||||
|
.x(45, pct).y(50, pct)
|
||||||
|
.center()
|
||||||
|
.backgroundColor("var(--accent")
|
||||||
|
.transition("transform 1.75s ease-in-out")
|
||||||
|
.onAppear(function () {
|
||||||
|
let growing = true;
|
||||||
|
|
||||||
|
setInterval(() => {
|
||||||
|
if (growing) {
|
||||||
|
this.style.transform = "scale(1.5)";
|
||||||
|
} else {
|
||||||
|
this.style.transform = "scale(0.7)";
|
||||||
|
}
|
||||||
|
growing = !growing;
|
||||||
|
}, 750);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.gap(1, em)
|
||||||
|
.position("relative")
|
||||||
|
.overflow("scroll")
|
||||||
|
.height(95, pct)
|
||||||
|
.width(93, pct)
|
||||||
|
.paddingTop(2, em)
|
||||||
|
.paddingBottom(2, em)
|
||||||
|
.paddingHorizontal(2, em)
|
||||||
|
.backgroundColor("var(--darkbrown)")
|
||||||
|
.onAppear(async () => {
|
||||||
|
console.log("appear")
|
||||||
|
let res = await Socket.send({app: "FORUM", operation: "GET", msg: {forum: "HY", number: 100}})
|
||||||
|
if(!res) console.error("failed to get messages")
|
||||||
|
if(res.msg.length > 0 && this.messages.length === 0) {
|
||||||
|
console.log("rerendering", res.msg)
|
||||||
|
this.messages = res.msg
|
||||||
|
this.rerender()
|
||||||
|
}
|
||||||
|
window.addEventListener("new-message", (e) => {
|
||||||
|
this.messages = e.detail
|
||||||
|
this.rerender()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.scrollTop = this.scrollHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
formatTime(str) {
|
||||||
|
// Extract the time part with am/pm
|
||||||
|
const match = str.match(/-(\d+:\d+):\d+.*(am|pm)/i);
|
||||||
|
if (!match) return null;
|
||||||
|
|
||||||
|
const [_, hourMin, ampm] = match;
|
||||||
|
return hourMin + ampm.toLowerCase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
register(MessagesPanel)
|
||||||
@@ -9,14 +9,13 @@ css(`
|
|||||||
app-menu.minimized {
|
app-menu.minimized {
|
||||||
color: var(--accent);
|
color: var(--accent);
|
||||||
transform: translate(-50%, 65%);
|
transform: translate(-50%, 65%);
|
||||||
border: 1px solid var(--accent);
|
border: 0.2px solid var(--accent);
|
||||||
padding-top: 0.5em;
|
padding-top: 0.5em;
|
||||||
padding-left: 2em;
|
padding-left: 2em;
|
||||||
padding-right: 2em;
|
padding-right: 2em;
|
||||||
padding-bottom: 4em;
|
padding-bottom: 4em;
|
||||||
bottom: 1em;
|
bottom: 1em;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
background-color: var(--green);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
app-menu p {
|
app-menu p {
|
||||||
|
|||||||
@@ -10,16 +10,45 @@ class ProfileButton extends Shadow {
|
|||||||
.borderRadius(5, px)
|
.borderRadius(5, px)
|
||||||
|
|
||||||
ProfileMenu()
|
ProfileMenu()
|
||||||
|
.x(0, vw).y(4, em)
|
||||||
|
.center()
|
||||||
})
|
})
|
||||||
.display("block")
|
.display("block")
|
||||||
.onHover((hovering) => {
|
.onAppear(async () => {
|
||||||
|
try {
|
||||||
|
const res = await fetch("/profile", {
|
||||||
|
method: "GET",
|
||||||
|
credentials: "include",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok) throw new Error("Failed to fetch profile");
|
||||||
|
|
||||||
|
const profile = await res.json();
|
||||||
|
console.log(profile);
|
||||||
|
return profile;
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.onHover((hovering, e) => {
|
||||||
console.log(hovering)
|
console.log(hovering)
|
||||||
if(hovering) {
|
console.log(e.target)
|
||||||
|
if(hovering && !e.target.matches("profile-menu")) {
|
||||||
this.$("img").backgroundColor("var(--accent)")
|
this.$("img").backgroundColor("var(--accent)")
|
||||||
this.$("img").border("1px solid black")
|
this.$("img").style.outline = "1px solid black"
|
||||||
} else {
|
} else if(!e.target.matches("profile-menu")) {
|
||||||
this.$("img").backgroundColor("")
|
this.$("img").backgroundColor("")
|
||||||
this.$("img").border("")
|
this.$("img").style.outline = ""
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.onClick((done) => {
|
||||||
|
console.log(done)
|
||||||
|
if(done) {
|
||||||
|
this.$("profile-menu").style.display = ""
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,51 +1,11 @@
|
|||||||
css(`
|
|
||||||
profile-menu {
|
|
||||||
position: absolute;
|
|
||||||
right: -.5em;
|
|
||||||
top: -.3em;
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
background-color: rgba(255, 223, 180, 0.56);
|
|
||||||
color: black;
|
|
||||||
transition: width 0.2s, height 0.2s;
|
|
||||||
border-radius: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
profile-menu * {
|
|
||||||
transition: opacity 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
profile-menu.closed * {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
profile-menu.open {
|
|
||||||
width: 10em;
|
|
||||||
height: 15em;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
profile-menu.open * {
|
|
||||||
opacity: 100%;
|
|
||||||
}
|
|
||||||
`)
|
|
||||||
|
|
||||||
class ProfileMenu extends Shadow {
|
class ProfileMenu extends Shadow {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
ZStack(() => {
|
ZStack(() => {
|
||||||
if(this.classList.contains("open")) {
|
p("profile")
|
||||||
setTimeout(() => {
|
|
||||||
// this.innerHTML = /* html */`
|
|
||||||
// <a href="signout" style="position: absolute; left: 0px; color: white; z-index: 2">sign out</a>
|
|
||||||
// <p>Profile</p>
|
|
||||||
// `
|
|
||||||
}, 200)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
util.observeClassChange(this, (cl) => {
|
|
||||||
})
|
})
|
||||||
|
.backgroundColor("var(--accent)")
|
||||||
|
.display("none")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
<link rel="icon" href="/_/icons/logo.svg">
|
<link rel="icon" href="/_/icons/logo.svg">
|
||||||
<link rel="stylesheet" href="/_/code/shared.css">
|
<link rel="stylesheet" href="/_/code/shared.css">
|
||||||
<script src="/_/code/quill.js"></script>
|
<script src="/_/code/quill.js"></script>
|
||||||
|
<script src="/_/code/zod.js"></script>
|
||||||
<script type="module" src="75820185/index.js"></script>
|
<script type="module" src="75820185/index.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body style="margin: 0px">
|
<body style="margin: 0px">
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import ConnectionHandler from "./ws/ConnectionHandler.js"
|
import Socket from "./ws/Socket.js"
|
||||||
window.ConnectionHandler = new ConnectionHandler()
|
|
||||||
|
|
||||||
import "./components/Home.js"
|
import "./components/Home.js"
|
||||||
|
|
||||||
|
window.Socket = new Socket()
|
||||||
Home()
|
Home()
|
||||||
@@ -4,8 +4,9 @@ class Connection {
|
|||||||
linkCreated;
|
linkCreated;
|
||||||
wsStatus;
|
wsStatus;
|
||||||
|
|
||||||
constructor() {
|
constructor(receiveCB) {
|
||||||
this.init()
|
this.init()
|
||||||
|
this.receiveCB = receiveCB
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
@@ -17,41 +18,13 @@ class Connection {
|
|||||||
this.ws.addEventListener('open', () => {
|
this.ws.addEventListener('open', () => {
|
||||||
this.connectionTries = 0
|
this.connectionTries = 0
|
||||||
console.log("Websocket connection established.");
|
console.log("Websocket connection established.");
|
||||||
this.ws.addEventListener('message', this.onMessage)
|
this.ws.addEventListener('message', this.receiveCB)
|
||||||
});
|
});
|
||||||
this.ws.addEventListener("close", () => {
|
this.ws.addEventListener("close", () => {
|
||||||
this.checkOpen();
|
this.checkOpen();
|
||||||
console.log('Websocket Closed')
|
console.log('Websocket Closed')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
onMessage = (event) => {
|
|
||||||
let message = event.data;
|
|
||||||
let messageSplit = message.split('|');
|
|
||||||
|
|
||||||
switch(messageSplit[0]) {
|
|
||||||
case 'connected':
|
|
||||||
Forms.connected(messageSplit[1], messageSplit[2])
|
|
||||||
break;
|
|
||||||
case 'disconnected':
|
|
||||||
Forms.disconnected(messageSplit[1])
|
|
||||||
break;
|
|
||||||
case 'UPDATE-SIZE':
|
|
||||||
updateFileSize(messageSplit[1], messageSplit[2]);
|
|
||||||
break;
|
|
||||||
case 'URL-TITLE-RESPONSE':
|
|
||||||
this.updateLinkTitle(messageSplit[1]);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
console.log('Websocket message:', message);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (messageSplit[1] == 'CSS-LINK') {
|
|
||||||
this.createCSSLink(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async checkOpen() {
|
async checkOpen() {
|
||||||
if (this.ws.readyState === WebSocket.OPEN) {
|
if (this.ws.readyState === WebSocket.OPEN) {
|
||||||
@@ -70,65 +43,20 @@ class Connection {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
sendMessage = (msg) => {
|
send = (msg) => {
|
||||||
|
console.log("sending")
|
||||||
if (this.ws.readyState === WebSocket.OPEN) {
|
if (this.ws.readyState === WebSocket.OPEN) {
|
||||||
this.ws.send(msg);
|
this.ws.send(msg);
|
||||||
}
|
}
|
||||||
|
else if(this.connectionTries === 0) {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.send(msg)
|
||||||
|
}, 100)
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
console.log('No websocket connection: Cannot send message');
|
console.error('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
|
export default Connection
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
46
ui/site/ws/Socket.js
Normal file
46
ui/site/ws/Socket.js
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import Connection from "./Connection.js";
|
||||||
|
|
||||||
|
export default class Socket {
|
||||||
|
connection;
|
||||||
|
disabled = true;
|
||||||
|
requestID = 1;
|
||||||
|
pending = new Map();
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.connection = new Connection(this.receive);
|
||||||
|
}
|
||||||
|
|
||||||
|
isOpen() {
|
||||||
|
if(this.connection.checkOpen()) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
send(msg) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const id = (++this.requestID).toString();
|
||||||
|
this.pending.set(id, resolve);
|
||||||
|
this.connection.send(JSON.stringify({ id, ...msg }));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
receive = (event) => {
|
||||||
|
const msg = JSON.parse(event.data);
|
||||||
|
if (msg.id && this.pending.has(msg.id)) {
|
||||||
|
this.pending.get(msg.id)(msg);
|
||||||
|
this.pending.delete(msg.id);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
this.onBroadcast(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onBroadcast(msg) {
|
||||||
|
console.log(msg.msg)
|
||||||
|
window.dispatchEvent(new CustomEvent(msg.event, {
|
||||||
|
detail: msg.msg
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user