add mailboxes

This commit is contained in:
marlon-erler
2024-07-08 21:13:56 +02:00
parent 85164a4e0a
commit b88959e135
16 changed files with 342 additions and 79 deletions
+4
View File
@@ -6,6 +6,7 @@ import {
getWebSocketStats, getWebSocketStats,
processMessage, processMessage,
reconnectServers, reconnectServers,
removeExpiredMailboxes,
trackConnection, trackConnection,
} from "./websocket-handler"; } from "./websocket-handler";
import { import {
@@ -138,8 +139,11 @@ async function main() {
// LOOP // LOOP
setInterval(() => { setInterval(() => {
if (process.env.DEV != "1") {
updateCLI(); updateCLI();
reconnectServers(config); reconnectServers(config);
}
removeExpiredMailboxes();
}, 2000); }, 2000);
// FINISH // FINISH
BIN
View File
Binary file not shown.
+58 -4
View File
@@ -131,6 +131,19 @@
); );
return proxyState; return proxyState;
} }
function persistState(localStorageKey, state) {
state.subscribe(() => {
const stringifiedValue = state.toString();
localStorage.setItem(localStorageKey, stringifiedValue);
});
}
function restoreState(localStorageKey, initialStateValue) {
const storedString = localStorage.getItem(localStorageKey) ?? JSON.stringify(initialStateValue);
const convertedValue = JSON.parse(storedString);
const state = new State(convertedValue);
persistState(localStorageKey, state);
return state;
}
function createElement(tagName, attributes = {}, ...children) { function createElement(tagName, attributes = {}, ...children) {
const element = document.createElement(tagName); const element = document.createElement(tagName);
if (attributes != null) if (attributes != null)
@@ -159,13 +172,13 @@
try { try {
const [listState, toElement] = value; const [listState, toElement] = value;
listState.handleAddition((newItem) => { listState.handleAddition((newItem) => {
const child = toElement(newItem, listState); const child = toElement(newItem);
listState.handleRemoval( listState.handleRemoval(
newItem, newItem,
() => child.remove() () => child.remove()
); );
element.append(child); element.append(child);
child.scrollIntoView(); element.scrollTop = element.scrollHeight;
}); });
} catch { } catch {
throw `error: cannot process subscribe:children directive because ListItemConverter is not defined. Usage: "subscribe:children={[list, converter]}"; you can find a more detailed example in the documentation`; throw `error: cannot process subscribe:children directive because ListItemConverter is not defined. Usage: "subscribe:children={[list, converter]}"; you can find a more detailed example in the documentation`;
@@ -225,6 +238,8 @@
}; };
messageHandler = (data) => { messageHandler = (data) => {
}; };
mailboxHandler = (mailboxId2) => {
};
// init // init
set onconnect(handler) { set onconnect(handler) {
this.connectionHandler = handler; this.connectionHandler = handler;
@@ -235,6 +250,9 @@
set onmessage(handler) { set onmessage(handler) {
this.messageHandler = handler; this.messageHandler = handler;
} }
set onmailbox(handler) {
this.mailboxHandler = handler;
}
// utility methods // utility methods
send(messageObject) { send(messageObject) {
if (this.ws == void 0) return false; if (this.ws == void 0) return false;
@@ -244,15 +262,23 @@
} }
// public methods // public methods
connect(address) { connect(address) {
this.disconnect();
this.ws = new WebSocket(address); this.ws = new WebSocket(address);
this.ws.addEventListener("open", this.connectionHandler); this.ws.addEventListener("open", this.connectionHandler);
this.ws.addEventListener("close", this.disconnectionHandler); this.ws.addEventListener("close", this.disconnectionHandler);
this.ws.addEventListener("message", (message) => { this.ws.addEventListener("message", (message) => {
const dataString = message.data.toString(); const dataString = message.data.toString();
const data = JSON.parse(dataString); const data = JSON.parse(dataString);
if (data.assignedMailboxId) {
return this.mailboxHandler(data.assignedMailboxId);
} else {
this.messageHandler(data); this.messageHandler(data);
}
}); });
} }
disconnect() {
this.ws?.close();
}
sendMessage(channel, body) { sendMessage(channel, body) {
const messageObject = { const messageObject = {
messageChannel: channel, messageChannel: channel,
@@ -272,10 +298,24 @@
}; };
return this.send(messageObject); return this.send(messageObject);
} }
requestMailbox() {
const messageObject = {
requestingMailboxSetup: true
};
return this.send(messageObject);
}
accessMailbox(mailboxId2) {
const messageObject = {
requestedMailbox: mailboxId2
};
return this.send(messageObject);
}
}; };
// src/translations.tsx // src/translations.tsx
var staticTextEnglish = { var staticTextEnglish = {
mailbox: "Mailbox",
requestMailbox: "Request Mailbox",
channel: "Channel", channel: "Channel",
channel_placeholder: "my-channel", channel_placeholder: "my-channel",
disconnected: "Server disconnected", disconnected: "Server disconnected",
@@ -298,6 +338,8 @@
var translations = { var translations = {
en: staticTextEnglish, en: staticTextEnglish,
es: { es: {
mailbox: "Buz\xF3n",
requestMailbox: "Solicitar buz\xF3n",
channel: "Canal", channel: "Canal",
channel_placeholder: "mi-canal", channel_placeholder: "mi-canal",
disconnected: "Sin coneci\xF3n", disconnected: "Sin coneci\xF3n",
@@ -318,6 +360,8 @@
unsubscribe: "Cancelar" unsubscribe: "Cancelar"
}, },
de: { de: {
mailbox: "Briefkasten",
requestMailbox: "Briefkasten beantragen",
channel: "Kanal", channel: "Kanal",
channel_placeholder: "mein-kanal", channel_placeholder: "mein-kanal",
disconnected: "Verbindung getrennt", disconnected: "Verbindung getrennt",
@@ -352,12 +396,15 @@
this.channel = channel; this.channel = channel;
this.body = body; this.body = body;
} }
uuid = new UUID(); id = UUID();
}; };
var UDN = new UDNFrontend(); var UDN = new UDNFrontend();
UDN.onconnect = () => { UDN.onconnect = () => {
isDisconnected.value = false; isDisconnected.value = false;
updateStats(); updateStats();
if (mailboxId.value != "") {
UDN.accessMailbox(mailboxId.value);
}
}; };
UDN.onmessage = (data) => { UDN.onmessage = (data) => {
lastReceivedMessage.value = JSON.stringify(data, null, 4); lastReceivedMessage.value = JSON.stringify(data, null, 4);
@@ -367,6 +414,9 @@
if (messageBody) messages.add(messageObject); if (messageBody) messages.add(messageObject);
} }
}; };
UDN.onmailbox = (id) => {
mailboxId.value = id;
};
UDN.ondisconnect = () => { UDN.ondisconnect = () => {
isDisconnected.value = true; isDisconnected.value = true;
setTimeout(connect, 2e3); setTimeout(connect, 2e3);
@@ -376,6 +426,7 @@
} }
connect(); connect();
var isDisconnected = new State(true); var isDisconnected = new State(true);
var mailboxId = restoreState("mailbox-id", "");
var messages = new ListState(); var messages = new ListState();
var lastReceivedMessage = new State( var lastReceivedMessage = new State(
getText("noMessagesReceived") getText("noMessagesReceived")
@@ -411,6 +462,9 @@
UDN.sendMessage(newMessageChannel.value, newMessageBody.value); UDN.sendMessage(newMessageChannel.value, newMessageBody.value);
newMessageBody.value = ""; newMessageBody.value = "";
} }
function requestMailbox() {
UDN.requestMailbox();
}
// src/infoScreen.tsx // src/infoScreen.tsx
function InfoScreen() { function InfoScreen() {
@@ -419,7 +473,7 @@
// src/mainScreen.tsx // src/mainScreen.tsx
function MainScreen() { function MainScreen() {
return /* @__PURE__ */ createElement("article", { id: "main-screen" }, /* @__PURE__ */ createElement("header", null, getText("settings")), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("div", { class: "tile width-input" }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("b", null, getText("messageLastReceived")), /* @__PURE__ */ createElement("p", { class: "secondary", "subscribe:innerText": lastReceivedMessage }))), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("label", { class: "tile" }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, getText("subscribeToChannel")), /* @__PURE__ */ createElement( return /* @__PURE__ */ createElement("article", { id: "main-screen" }, /* @__PURE__ */ createElement("header", null, getText("settings")), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("div", { class: "tile width-input" }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("b", null, getText("messageLastReceived")), /* @__PURE__ */ createElement("p", { class: "secondary", "subscribe:innerText": lastReceivedMessage }))), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "tile width-input" }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("b", null, getText("mailbox")), /* @__PURE__ */ createElement("p", { class: "secondary", "subscribe:innerText": mailboxId }))), /* @__PURE__ */ createElement("div", { class: "flex-row width-input justify-end" }, /* @__PURE__ */ createElement("button", { class: "primary width-100", "on:click": requestMailbox }, getText("requestMailbox"), /* @__PURE__ */ createElement("span", { class: "icon" }, "inbox"))), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("label", { class: "tile" }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, getText("subscribeToChannel")), /* @__PURE__ */ createElement(
"input", "input",
{ {
placeholder: getText("channel_placeholder"), placeholder: getText("channel_placeholder"),
+18
View File
@@ -21,6 +21,7 @@ touch src/index.tsx dist/index.html
``` ```
The following build script will be enough: The following build script will be enough:
```JSON ```JSON
{ {
"scripts": { "scripts": {
@@ -102,6 +103,7 @@ listState.remove(newItem);
## Persistence ## Persistence
States can persist through reloads via LocalStorage. To implement this, modify your code like this: States can persist through reloads via LocalStorage. To implement this, modify your code like this:
```diff ```diff
-const myState = new React.State("hello"); -const myState = new React.State("hello");
+const myState = React.restoreState("my-state", "hello"); +const myState = React.restoreState("my-state", "hello");
@@ -213,44 +215,60 @@ document.body.append(
``` ```
# Changelog # Changelog
## 1.1.0 ## 1.1.0
- Improve code - Improve code
- Add missing `break` statements for directives - Add missing `break` statements for directives
- Add `toggle` directive - Add `toggle` directive
- Improve documentation - Improve documentation
## 1.1.1 ## 1.1.1
- Improve documentation - Improve documentation
## 1.1.2 ## 1.1.2
- Remove `console.log()` calls - Remove `console.log()` calls
- Improve documentation - Improve documentation
## 1.1.3 ## 1.1.3
- Add `on:enter` directive - Add `on:enter` directive
## 1.2.0 ## 1.2.0
**BREAKING CHANGES** **BREAKING CHANGES**
- Replace UUID class with function; - Replace UUID class with function;
- `new UUID()` => `UUID() returns string` - `new UUID()` => `UUID() returns string`
- Replace definition of `Identifiable` - Replace definition of `Identifiable`
- now has `id: string` instead of `uuid: UUID()` - now has `id: string` instead of `uuid: UUID()`
Other changes: Other changes:
- Add State persistence - Add State persistence
- Add error description when utilizing `subscribe:children` incorrectly - Add error description when utilizing `subscribe:children` incorrectly
- Fix bug where ListState subscriptions were not called - Fix bug where ListState subscriptions were not called
- Improve documentation - Improve documentation
## 1.2.1 ## 1.2.1
- Add `set:<attribute>` directive - Add `set:<attribute>` directive
## 1.2.2 ## 1.2.2
- Remove list parameter from `ListItemConverter` - Remove list parameter from `ListItemConverter`
- Add `clear()` method to `ListState` - Add `clear()` method to `ListState`
## 1.2.3 ## 1.2.3
- Allow `toggle:<attribute>` to be used without a state - Allow `toggle:<attribute>` to be used without a state
## 1.2.4 ## 1.2.4
- On `subscribe:children`, scroll new elements into view - On `subscribe:children`, scroll new elements into view
## 1.2.5
- Instead of using `scrollIntoView()`, `subscribe:children` now scrolls to the bottom
+3 -6
View File
@@ -205,19 +205,16 @@ export function createElement(
try { try {
const [listState, toElement] = value as [ const [listState, toElement] = value as [
listState: ListState<any>, listState: ListState<any>,
( ListItemConverter<any>
item: any,
listState: ListState<any>
) => HTMLElement
]; ];
listState.handleAddition((newItem) => { listState.handleAddition((newItem) => {
const child = toElement(newItem, listState); const child = toElement(newItem);
listState.handleRemoval(newItem, () => listState.handleRemoval(newItem, () =>
child.remove() child.remove()
); );
element.append(child); element.append(child);
child.scrollIntoView(); element.scrollTop = element.scrollHeight;
}); });
} catch { } catch {
throw `error: cannot process subscribe:children directive because ListItemConverter is not defined. Usage: "subscribe:children={[list, converter]}"; you can find a more detailed example in the documentation`; throw `error: cannot process subscribe:children directive because ListItemConverter is not defined. Usage: "subscribe:children={[list, converter]}"; you can find a more detailed example in the documentation`;
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "bloatless-react", "name": "bloatless-react",
"version": "1.2.4", "version": "1.2.5-beta1",
"description": "very minimal and flexible react alternative", "description": "very minimal and flexible react alternative",
"repository": { "repository": {
"type": "git", "type": "git",
+19 -2
View File
@@ -35,10 +35,18 @@ UDN.unsubscribe("my-channel");
```TypeScript ```TypeScript
export interface Message { export interface Message {
// connecting to server
requestingServerConnection?: boolean;
// mailbox
requestingMailboxSetup?: boolean;
assignedMailboxId?: string; // sent by server
requestedMailbox?: string;
// subscribing to channel // subscribing to channel
subscribeChannel?: string; subscribeChannel?: string;
unsubscribeChannel?: string; unsubscribeChannel?: string;
subscribed?: boolean; // sent by the server to confirm subscription subscribed?: boolean; // sent by server to confirm subscription
//sending message //sending message
messageChannel?: string; messageChannel?: string;
@@ -46,7 +54,7 @@ export interface Message {
} }
export default class UDNFrontend { export default class UDNFrontend {
private ws: WebSocket | undefined; ws: WebSocket | undefined;
// handlers // handlers
private connectionHandler = () => {}; private connectionHandler = () => {};
@@ -60,6 +68,7 @@ export default class UDNFrontend {
// methods // methods
connect(address: string): void; connect(address: string): void;
disconnect(address: string): void;
sendMessage(channel: string, body: string): boolean; // true if message is sent sendMessage(channel: string, body: string): boolean; // true if message is sent
subscribe(channel: string): boolean; // true if request is sent, NOT inherently when subscribed subscribe(channel: string): boolean; // true if request is sent, NOT inherently when subscribed
unsubscribe(channel: string): boolean; // true if request is sent, NOT inherently when unsubscribed unsubscribe(channel: string): boolean; // true if request is sent, NOT inherently when unsubscribed
@@ -75,3 +84,11 @@ export default class UDNFrontend {
## 1.0.2 ## 1.0.2
- minor fixes - minor fixes
## 1.0.3
- add `disconnect()` method
## 1.0.4
- make ws public
+40 -3
View File
@@ -1,8 +1,16 @@
export interface Message { export interface Message {
// connecting to server
requestingServerConnection?: boolean;
// mailbox
requestingMailboxSetup?: boolean;
assignedMailboxId?: string; // sent by server
requestedMailbox?: string;
// subscribing to channel // subscribing to channel
subscribeChannel?: string; subscribeChannel?: string;
unsubscribeChannel?: string; unsubscribeChannel?: string;
subscribed?: boolean; // sent by the server to confirm subscription subscribed?: boolean; // sent by server to confirm subscription
//sending message //sending message
messageChannel?: string; messageChannel?: string;
@@ -10,12 +18,13 @@ export interface Message {
} }
export default class UDNFrontend { export default class UDNFrontend {
private ws: WebSocket | undefined; ws: WebSocket | undefined;
// handlers // handlers
private connectionHandler = () => {}; private connectionHandler = () => {};
private disconnectionHandler = () => {}; private disconnectionHandler = () => {};
private messageHandler = (data: Message) => {}; private messageHandler = (data: Message) => {};
private mailboxHandler = (mailboxId: string) => {};
// init // init
set onconnect(handler: () => void) { set onconnect(handler: () => void) {
@@ -27,9 +36,12 @@ export default class UDNFrontend {
set onmessage(handler: (data: Message) => void) { set onmessage(handler: (data: Message) => void) {
this.messageHandler = handler; this.messageHandler = handler;
} }
set onmailbox(handler: (mailboxId: string) => void) {
this.mailboxHandler = handler;
}
// utility methods // utility methods
private send(messageObject: Object): boolean { private send(messageObject: Message): boolean {
if (this.ws == undefined) return false; if (this.ws == undefined) return false;
const messageString = JSON.stringify(messageObject); const messageString = JSON.stringify(messageObject);
@@ -39,16 +51,27 @@ export default class UDNFrontend {
// public methods // public methods
connect(address: string): void { connect(address: string): void {
this.disconnect();
this.ws = new WebSocket(address); this.ws = new WebSocket(address);
this.ws.addEventListener("open", this.connectionHandler); this.ws.addEventListener("open", this.connectionHandler);
this.ws.addEventListener("close", this.disconnectionHandler); this.ws.addEventListener("close", this.disconnectionHandler);
this.ws.addEventListener("message", (message) => { this.ws.addEventListener("message", (message) => {
const dataString = message.data.toString(); const dataString = message.data.toString();
const data = JSON.parse(dataString); const data = JSON.parse(dataString);
if (data.assignedMailboxId) {
return this.mailboxHandler(data.assignedMailboxId);
} else {
this.messageHandler(data); this.messageHandler(data);
}
}); });
} }
disconnect(): void {
this.ws?.close();
}
sendMessage(channel: string, body: string): boolean { sendMessage(channel: string, body: string): boolean {
const messageObject = { const messageObject = {
messageChannel: channel, messageChannel: channel,
@@ -70,4 +93,18 @@ export default class UDNFrontend {
}; };
return this.send(messageObject); return this.send(messageObject);
} }
requestMailbox(): boolean {
const messageObject = {
requestingMailboxSetup: true,
};
return this.send(messageObject);
}
accessMailbox(mailboxId: string): boolean {
const messageObject = {
requestedMailbox: mailboxId,
};
return this.send(messageObject);
}
} }
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "udn-frontend", "name": "udn-frontend",
"version": "1.0.2", "version": "1.0.4-beta2",
"description": "TypeScript library for UDN apps", "description": "TypeScript library for UDN apps",
"main": "index.ts", "main": "index.ts",
"scripts": { "scripts": {
+2 -2
View File
@@ -1,8 +1,8 @@
{ {
"dependencies": { "dependencies": {
"bloatless-react": "^1.2.4", "bloatless-react": "1.2.5-beta1",
"esbuild": "latest", "esbuild": "latest",
"udn-frontend": "^1.0.2" "udn-frontend": "1.0.4-beta2"
}, },
"scripts": { "scripts": {
"build": "esbuild src/index.tsx --bundle --outdir=dist" "build": "esbuild src/index.tsx --bundle --outdir=dist"
+18
View File
@@ -3,8 +3,10 @@ import * as React from "bloatless-react";
import { import {
isMessageEmpty, isMessageEmpty,
lastReceivedMessage, lastReceivedMessage,
mailboxId,
newMessageBody, newMessageBody,
newMessageChannel, newMessageChannel,
requestMailbox,
sendMessage, sendMessage,
subscribe, subscribe,
subscriptionChannel, subscriptionChannel,
@@ -28,6 +30,22 @@ export function MainScreen() {
<hr></hr> <hr></hr>
<div class="tile width-input">
<div>
<b>{getText("mailbox")}</b>
<p class="secondary" subscribe:innerText={mailboxId}></p>
</div>
</div>
<div class="flex-row width-input justify-end">
<button class="primary width-100" on:click={requestMailbox}>
{getText("requestMailbox")}
<span class="icon">inbox</span>
</button>
</div>
<hr></hr>
<label class="tile"> <label class="tile">
<div> <div>
<span>{getText("subscribeToChannel")}</span> <span>{getText("subscribeToChannel")}</span>
+13 -1
View File
@@ -4,7 +4,7 @@ import UDNFrontend from "udn-frontend";
import { getText } from "./translations"; import { getText } from "./translations";
export class Message implements React.Identifiable { export class Message implements React.Identifiable {
uuid = new React.UUID(); id = React.UUID();
constructor(public channel: string, public body: string) {} constructor(public channel: string, public body: string) {}
} }
@@ -15,6 +15,9 @@ const UDN = new UDNFrontend();
UDN.onconnect = () => { UDN.onconnect = () => {
isDisconnected.value = false; isDisconnected.value = false;
updateStats(); updateStats();
if (mailboxId.value != "") {
UDN.accessMailbox(mailboxId.value);
}
}; };
UDN.onmessage = (data) => { UDN.onmessage = (data) => {
@@ -27,6 +30,10 @@ UDN.onmessage = (data) => {
} }
}; };
UDN.onmailbox = (id) => {
mailboxId.value = id;
};
UDN.ondisconnect = () => { UDN.ondisconnect = () => {
isDisconnected.value = true; isDisconnected.value = true;
setTimeout(connect, 2000); setTimeout(connect, 2000);
@@ -40,6 +47,7 @@ connect();
// STATE // STATE
// connection // connection
export const isDisconnected = new React.State(true); export const isDisconnected = new React.State(true);
export const mailboxId = React.restoreState("mailbox-id", "");
// messages // messages
export const messages = new React.ListState<Message>(); export const messages = new React.ListState<Message>();
@@ -93,3 +101,7 @@ export function sendMessage() {
UDN.sendMessage(newMessageChannel.value, newMessageBody.value); UDN.sendMessage(newMessageChannel.value, newMessageBody.value);
newMessageBody.value = ""; newMessageBody.value = "";
} }
export function requestMailbox() {
UDN.requestMailbox();
}
+6
View File
@@ -1,5 +1,7 @@
// STATIC // STATIC
export const staticTextEnglish = { export const staticTextEnglish = {
mailbox: "Mailbox",
requestMailbox: "Request Mailbox",
channel: "Channel", channel: "Channel",
channel_placeholder: "my-channel", channel_placeholder: "my-channel",
disconnected: "Server disconnected", disconnected: "Server disconnected",
@@ -23,6 +25,8 @@ export const staticTextEnglish = {
export const translations: { [key: string]: typeof staticTextEnglish } = { export const translations: { [key: string]: typeof staticTextEnglish } = {
en: staticTextEnglish, en: staticTextEnglish,
es: { es: {
mailbox: "Buzón",
requestMailbox: "Solicitar buzón",
channel: "Canal", channel: "Canal",
channel_placeholder: "mi-canal", channel_placeholder: "mi-canal",
disconnected: "Sin coneción", disconnected: "Sin coneción",
@@ -43,6 +47,8 @@ export const translations: { [key: string]: typeof staticTextEnglish } = {
unsubscribe: "Cancelar", unsubscribe: "Cancelar",
}, },
de: { de: {
mailbox: "Briefkasten",
requestMailbox: "Briefkasten beantragen",
channel: "Kanal", channel: "Kanal",
channel_placeholder: "mein-kanal", channel_placeholder: "mein-kanal",
disconnected: "Verbindung getrennt", disconnected: "Verbindung getrennt",
+59 -9
View File
@@ -1,3 +1,9 @@
import {
WebSocketMessage,
sendWebSocketMessage,
subscriptionMap,
} from "./websocket-handler";
import Crypto from "crypto"; import Crypto from "crypto";
import { WS } from "./index"; import { WS } from "./index";
@@ -6,19 +12,38 @@ export interface Subscriber {
} }
export class Mailbox implements Subscriber { export class Mailbox implements Subscriber {
// basic // basic data
id = Crypto.randomUUID(); id = Crypto.randomUUID();
dateLastChecked = new Date(); dateLastChecked = new Date();
messages = new Set<string>();
get expiryDate() { get expiryDate() {
const expiryDate = new Date(this.dateLastChecked); const expiryDate = new Date(this.dateLastChecked);
expiryDate.setDate(expiryDate.getDate() + 7); TODO expiryDate.setDate(expiryDate.getDate() + 7);
return expiryDate; return expiryDate;
} }
// init
constructor(ws: WS) {
this.ws = ws;
this.sendId();
}
sendId(): void {
if (!this.ws) return;
sendWebSocketMessage(
{
assignedMailboxId: this.id,
},
this.ws
);
}
// storage
messages = new Set<string>();
// websocket // websocket
_ws: WS | undefined; private _ws: WS | undefined;
get ws(): WS | undefined { get ws(): WS | undefined {
return this._ws; return this._ws;
@@ -26,19 +51,36 @@ export class Mailbox implements Subscriber {
set ws(ws: WS) { set ws(ws: WS) {
this._ws = ws; this._ws = ws;
this.dateLastChecked = new Date();
this.sendUnreadMessages(); this.sendUnreadMessages();
const channels = subscriptionMap.getChannelList(ws);
channels?.forEach((channel) => this.subscribe(channel));
subscriptionMap.deleteSubscriber(ws);
}
// subscribing
subscribe(channelName: string): void {
subscriptionMap.set(this, channelName);
}
unsubscribe(channelName: string): void {
subscriptionMap.delete(this, channelName);
} }
// sending // sending
send(message) { send(message: string): void {
if (this._ws) { if (this._ws?.readyState == 1) {
this._ws.send(message); this._ws.send(message);
} else { } else {
this.messages.add(message); this.messages.add(message);
} }
} }
sendUnreadMessages() { sendUnreadMessages(): void {
console.log(this.messages);
this.messages.forEach((message) => { this.messages.forEach((message) => {
if (!this._ws) return; if (!this._ws) return;
@@ -96,11 +138,19 @@ export class SubscriptionMap {
} }
} }
deleteSubscriber(subscriber: Subscriber): void {
this.getChannelList(subscriber)?.forEach((channel) => {
this.delete(subscriber, channel);
});
}
// messages // messages
forwardMessage(channels: string[], messageString: string): void { forwardMessage(channels: string[], messageObject: WebSocketMessage): void {
channels.forEach((channel) => { channels.forEach((channel) => {
const subscribers = this.getClientList(channel); const subscribers = this.getClientList(channel);
subscribers?.forEach((subscriber) => subscriber.send(messageString)); subscribers?.forEach((subscriber) => {
sendWebSocketMessage(messageObject, subscriber);
});
}); });
} }
} }
+18 -2
View File
@@ -1,9 +1,14 @@
import { PACKAGE_FILE, STATIC_DIR } from "."; import { Mailbox, Subscriber } from "./subscriptionHandler";
import { PACKAGE_FILE, STATIC_DIR, WS } from ".";
import {
WebSocketMessage,
mailboxIdsPerClient,
mailboxes,
} from "./websocket-handler";
import Colors from "colors"; import Colors from "colors";
import Fs from "fs/promises"; import Fs from "fs/promises";
import Path from "path"; import Path from "path";
import { WebSocketMessage } from "./websocket-handler";
// CONFIG // CONFIG
const STAT_INDENT = 25; const STAT_INDENT = 25;
@@ -65,3 +70,14 @@ export function stringifyMessage(messageObject: WebSocketMessage): string {
export function parseMessage(messageString: string): Object { export function parseMessage(messageString: string): Object {
return JSON.parse(messageString); return JSON.parse(messageString);
} }
// SUBSCRIBERS
export function getMailboxIfExists(ws: WS): Mailbox | undefined {
const id = mailboxIdsPerClient.get(ws);
if (!id) return;
return mailboxes.get(id);
}
export function getMostAppropriateSubscriber(ws: WS): Subscriber {
return getMailboxIfExists(ws) ?? ws;
}
+57 -23
View File
@@ -1,6 +1,8 @@
import { Subscriber, SubscriptionMap } from "./subscriptionHandler"; import { Mailbox, Subscriber, SubscriptionMap } from "./subscriptionHandler";
import { import {
doIfIsString, doIfIsString,
getMailboxIfExists,
getMostAppropriateSubscriber,
parseMessage, parseMessage,
stringifyMessage, stringifyMessage,
writeError, writeError,
@@ -39,6 +41,8 @@ const knownMessageIds = new Set<string>();
export const subscriptionMap = new SubscriptionMap(); export const subscriptionMap = new SubscriptionMap();
export const clientsConnected = new Set<WS>(); export const clientsConnected = new Set<WS>();
export const mailboxes = new Map<string, Mailbox>();
export const mailboxIdsPerClient = new Map<WS, string>();
export const serversConnectedAsClient = new Set<WS>(); export const serversConnectedAsClient = new Set<WS>();
export const serversConnected = new Set<WebSocket>(); export const serversConnected = new Set<WebSocket>();
@@ -55,11 +59,7 @@ export function trackConnection(ws: WS) {
export function forgetConnection(ws: WS): void { export function forgetConnection(ws: WS): void {
clientsConnected.delete(ws); clientsConnected.delete(ws);
serversConnectedAsClient.delete(ws); serversConnectedAsClient.delete(ws);
subscriptionMap.deleteSubscriber(ws);
const channels = subscriptionMap.getChannelList(ws);
channels?.forEach((channel) => {
subscriptionMap.delete(ws, channel);
});
} }
export function trackSubscription( export function trackSubscription(
@@ -78,6 +78,17 @@ export function forgetSubscription(
confirmSubscription(subscriber, channel); confirmSubscription(subscriber, channel);
} }
// AUDIT
export function getWebSocketStats(): [string, number][] {
return [
["channels", subscriptionMap.subscribersPerChannel.size],
["clients connected", clientsConnected.size],
["mailboxes", mailboxes.size],
["servers connected", getServerCount()],
["servers disconnected", serversDisconnected.size],
];
}
// OTHER SERVERS // OTHER SERVERS
export function connectServers(config: typeof defaultConfig): void { export function connectServers(config: typeof defaultConfig): void {
config.connectedServers.forEach((serverAddress) => { config.connectedServers.forEach((serverAddress) => {
@@ -138,16 +149,6 @@ export function subscribeChannel(server: WS, channel: string): void {
server.send(messageString); server.send(messageString);
} }
// AUDIT
export function getWebSocketStats(): [string, number][] {
return [
["channels", subscriptionMap.subscribersPerChannel.size],
["clients connected", clientsConnected.size],
["servers connected", getServerCount()],
["servers disconnected", serversDisconnected.size],
];
}
// PROCESS MESSAGE // PROCESS MESSAGE
export function processMessage( export function processMessage(
ws: WS, ws: WS,
@@ -169,10 +170,10 @@ export function processMessage(
// SUBSCRIPTIONS // SUBSCRIPTIONS
doIfIsString(messageObject.subscribeChannel, (subscribeChannel) => { doIfIsString(messageObject.subscribeChannel, (subscribeChannel) => {
trackSubscription(ws, subscribeChannel); trackSubscription(getMostAppropriateSubscriber(ws), subscribeChannel);
}); });
doIfIsString(messageObject.unsubscribeChannel, (unsubscribeChannel) => { doIfIsString(messageObject.unsubscribeChannel, (unsubscribeChannel) => {
forgetSubscription(ws, unsubscribeChannel); forgetSubscription(getMostAppropriateSubscriber(ws), unsubscribeChannel);
}); });
// MESSAGE // MESSAGE
@@ -181,6 +182,17 @@ export function processMessage(
sendMessage(messageChannel, messageObject.messageBody, messageObject.uuid!); sendMessage(messageChannel, messageObject.messageBody, messageObject.uuid!);
}); });
// MAILBOX
if (messageObject.requestingMailboxSetup == true) {
const mailbox = new Mailbox(ws);
mailboxes.set(mailbox.id, mailbox);
}
doIfIsString(messageObject.requestedMailbox, (requestedMailbox) => {
const mailbox = mailboxes.get(requestedMailbox);
if (!mailbox) return;
mailbox.ws = ws;
});
// OTHER SERVER // OTHER SERVER
if (messageObject.requestingServerConnection == true) { if (messageObject.requestingServerConnection == true) {
clientsConnected.delete(ws); clientsConnected.delete(ws);
@@ -189,18 +201,40 @@ export function processMessage(
} }
} }
// MAILBOX
export function removeExpiredMailboxes() {
const today = new Date();
mailboxes.forEach((mailbox) => {
if (mailbox.expiryDate > today) return;
removeMailbox(mailbox);
});
}
export function removeMailbox(mailbox: Mailbox) {
subscriptionMap.deleteSubscriber(mailbox);
mailboxes.delete(mailbox.id);
}
// MESSAGING // MESSAGING
export function sendWebSocketMessage(
messageObject: WebSocketMessage,
destination: Subscriber
): void {
const messageString = JSON.stringify(messageObject);
destination.send(messageString);
}
export function sendMessage( export function sendMessage(
messageChannel: string, messageChannel: string,
messageBody: string, messageBody: string,
uuid: string uuid: string
): void { ): void {
const channels = messageChannel.split("/"); const channels = messageChannel.split("/");
subscriptionMap.forwardMessage(channels, {
const messageObject = { uuid, messageChannel, messageBody }; uuid,
const messageString = stringifyMessage(messageObject); messageChannel,
messageBody,
subscriptionMap.forwardMessage(channels, messageString); });
} }
function requestServerConnection(ws: WS): void { function requestServerConnection(ws: WS): void {