From b88959e135eaa7f659d09cf58eb89808e324ca56 Mon Sep 17 00:00:00 2001 From: marlon-erler Date: Mon, 8 Jul 2024 21:13:56 +0200 Subject: [PATCH] add mailboxes --- index.ts | 8 +- static/bun.lockb | Bin 11417 -> 11451 bytes static/dist/index.js | 64 ++++++++++++-- static/node_modules/bloatless-react/README.md | 60 ++++++++----- static/node_modules/bloatless-react/index.ts | 9 +- .../node_modules/bloatless-react/package.json | 2 +- static/node_modules/udn-frontend/README.md | 21 ++++- static/node_modules/udn-frontend/index.ts | 45 +++++++++- static/node_modules/udn-frontend/package.json | 2 +- static/package.json | 4 +- static/src/mainScreen.tsx | 18 ++++ static/src/model.tsx | 14 ++- static/src/translations.tsx | 6 ++ subscriptionHandler.ts | 68 +++++++++++++-- utility.ts | 20 ++++- websocket-handler.ts | 80 +++++++++++++----- 16 files changed, 342 insertions(+), 79 deletions(-) diff --git a/index.ts b/index.ts index 5b7362e..837a31c 100755 --- a/index.ts +++ b/index.ts @@ -6,6 +6,7 @@ import { getWebSocketStats, processMessage, reconnectServers, + removeExpiredMailboxes, trackConnection, } from "./websocket-handler"; import { @@ -138,8 +139,11 @@ async function main() { // LOOP setInterval(() => { - updateCLI(); - reconnectServers(config); + if (process.env.DEV != "1") { + updateCLI(); + reconnectServers(config); + } + removeExpiredMailboxes(); }, 2000); // FINISH diff --git a/static/bun.lockb b/static/bun.lockb index 6bdac98cfde18fcdd3f31b94e3fbb61ff053c057..584bceceb770baec805da26cd8a8525533c0e41e 100755 GIT binary patch delta 1260 zcmYLJdq`7Z6u);qwxyv{=e#bRTJ!B~yQZm0TAE6T6QRcx(an?|N}-D)E7#M4isbWA z6qP{_O>H!aw1>_|38|zNlva{pDVf)C6a7WfpE}rA z(p=p>W*Pr1ZYrm?zYe;TxAsLy_BUzV!q~!{$6-~v*a|gkk)OS-3X`vyPESK1BARRa|sua z14uVcY^5EBr7>&|Fjl?4tFK8rChyWJ+O4{iQvK*9E17jC!(Kkx^K~yys)vDd7=kaz0@R1blUuJ z`OMIng{SAXG`IYcHEgi{h)%l|ZyD81WF*Y&2!H=Fv?fGN*-<@;W!K#vJ{u!5HCMMc zmU>#Q1jSDU_F9)aTO^4iULEzDWUKw&$Hd(Jms=OA==xwbdDP{1z5Tko#dB)gp@I14 z6``ubEng&-p63IutKRsYQUy5w{&PC=(RyO$B>Lx3>2Ls!=!B{i>#R5A-8eGK?eSaI z^NIqow-YEXaIx@?f#fUBGZ#e65P;W=mZ`jzYLMxXPnPV{$>np=BHWuU1)f%vpdzC9 ziRu++)}>68blT$@l8PiX7{k1Z5VTR}o5nMz1L~ZEq$EvIX52*;E9s zMI#W!Pk1Lbof(U_jx4v<1_VpRTn1_n8~zWwVM<&8 delta 1202 zcmYLIT};zp6#ZH^>2L%m6fAR$P5Cj%G7$`lx)~=3QHMMjCQ$=8q5^}!h$EmbAqHNi z;1?lAA*cZbe{tafOb8P&5{V-C0*g_CM(~FyMwA4jw`;rhq37It?z#Qi-uuLI)zYBT z7$d(79loCxOEfo^EC(g`{c*I_Ej$^iSpOl@bzo23Mp>hI+xcLGkQ^bWwIaXrRPhN^ zL?W~X;vB#bKoejnU?QK7w5uNy`7IQ0)|8E;PX~M=%4L8 z#1|L<*#_}^OarnFNI>==-{7D$;3OdXfI&c3f5^uzfUF<6ErfwkC?PaL2yWj+;zMLC zZXhY55Koc@=~+0x3BM&%r5<2uJ{GV7p?yAP2F9t_pYsCS?^9zXf};c&SJw@U^DY77 z>h?;7oD0}NpVteF@3PY01sLb;0mk)P^5=F32o4u8?xYjIGJXA2l;E`j<2rZyb4$Q{ z{S-T8;N`yuyfBjZzej`HBD0f5YJ4QckwkfK$?HcEEnn=}l-`js)qQ;U;L3sd)_+~^ zb9WTJXwY71I#z`w51+N$9dSnL@>hM&aJAx3N{7uIB!5I1V`^ucHe~D?OBxt0dsAc2 z3+Xyjv%NIKn%qKb9f6;#w){Nb)$cs>wSTLrvn}f!UKqQy6jbF{c%WbVsc5$PI{q74 zC(VWPt-_7rsqQv%-Le#ol;ROK^?5~80b#5AXX}Tu=evu@OZPq2~k`Tgy>IkJU z%qFVLOw)^7N%l>BR|__%nPg#Bo(E(#r_Gi;l@NMe;uf7Ap+Tr(rzv~aSo}Jb;>X6L!n^HlV>s5tP^C`Ku)&+8kjRYP%eW>Ig)jZqBIi|;~}uzdMp%d%i~=%QFP zgWtjPg=MXm+&g((vE3o+!a64^&K{ob%gz^-NSvTyl*0OZJ~rM72~Ynm>L#40VTc@D z4fi5^MRS25uu>ZxkzZVzXD_x^R3wyJ^9t;hCu|9Y<)t>e)rNn>tFTL}c0bm}$%rI8 H9Y6IS85KHR diff --git a/static/dist/index.js b/static/dist/index.js index 6830a29..d79a859 100644 --- a/static/dist/index.js +++ b/static/dist/index.js @@ -131,6 +131,19 @@ ); 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) { const element = document.createElement(tagName); if (attributes != null) @@ -159,13 +172,13 @@ try { const [listState, toElement] = value; listState.handleAddition((newItem) => { - const child = toElement(newItem, listState); + const child = toElement(newItem); listState.handleRemoval( newItem, () => child.remove() ); element.append(child); - child.scrollIntoView(); + element.scrollTop = element.scrollHeight; }); } 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`; @@ -225,6 +238,8 @@ }; messageHandler = (data) => { }; + mailboxHandler = (mailboxId2) => { + }; // init set onconnect(handler) { this.connectionHandler = handler; @@ -235,6 +250,9 @@ set onmessage(handler) { this.messageHandler = handler; } + set onmailbox(handler) { + this.mailboxHandler = handler; + } // utility methods send(messageObject) { if (this.ws == void 0) return false; @@ -244,15 +262,23 @@ } // public methods connect(address) { + this.disconnect(); this.ws = new WebSocket(address); this.ws.addEventListener("open", this.connectionHandler); this.ws.addEventListener("close", this.disconnectionHandler); this.ws.addEventListener("message", (message) => { const dataString = message.data.toString(); const data = JSON.parse(dataString); - this.messageHandler(data); + if (data.assignedMailboxId) { + return this.mailboxHandler(data.assignedMailboxId); + } else { + this.messageHandler(data); + } }); } + disconnect() { + this.ws?.close(); + } sendMessage(channel, body) { const messageObject = { messageChannel: channel, @@ -272,10 +298,24 @@ }; 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 var staticTextEnglish = { + mailbox: "Mailbox", + requestMailbox: "Request Mailbox", channel: "Channel", channel_placeholder: "my-channel", disconnected: "Server disconnected", @@ -298,6 +338,8 @@ var translations = { en: staticTextEnglish, es: { + mailbox: "Buz\xF3n", + requestMailbox: "Solicitar buz\xF3n", channel: "Canal", channel_placeholder: "mi-canal", disconnected: "Sin coneci\xF3n", @@ -318,6 +360,8 @@ unsubscribe: "Cancelar" }, de: { + mailbox: "Briefkasten", + requestMailbox: "Briefkasten beantragen", channel: "Kanal", channel_placeholder: "mein-kanal", disconnected: "Verbindung getrennt", @@ -352,12 +396,15 @@ this.channel = channel; this.body = body; } - uuid = new UUID(); + id = UUID(); }; var UDN = new UDNFrontend(); UDN.onconnect = () => { isDisconnected.value = false; updateStats(); + if (mailboxId.value != "") { + UDN.accessMailbox(mailboxId.value); + } }; UDN.onmessage = (data) => { lastReceivedMessage.value = JSON.stringify(data, null, 4); @@ -367,6 +414,9 @@ if (messageBody) messages.add(messageObject); } }; + UDN.onmailbox = (id) => { + mailboxId.value = id; + }; UDN.ondisconnect = () => { isDisconnected.value = true; setTimeout(connect, 2e3); @@ -376,6 +426,7 @@ } connect(); var isDisconnected = new State(true); + var mailboxId = restoreState("mailbox-id", ""); var messages = new ListState(); var lastReceivedMessage = new State( getText("noMessagesReceived") @@ -411,6 +462,9 @@ UDN.sendMessage(newMessageChannel.value, newMessageBody.value); newMessageBody.value = ""; } + function requestMailbox() { + UDN.requestMailbox(); + } // src/infoScreen.tsx function InfoScreen() { @@ -419,7 +473,7 @@ // src/mainScreen.tsx 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", { placeholder: getText("channel_placeholder"), diff --git a/static/node_modules/bloatless-react/README.md b/static/node_modules/bloatless-react/README.md index 5630017..10e43bb 100644 --- a/static/node_modules/bloatless-react/README.md +++ b/static/node_modules/bloatless-react/README.md @@ -21,6 +21,7 @@ touch src/index.tsx dist/index.html ``` The following build script will be enough: + ```JSON { "scripts": { @@ -102,6 +103,7 @@ listState.remove(newItem); ## Persistence States can persist through reloads via LocalStorage. To implement this, modify your code like this: + ```diff -const myState = new React.State("hello"); +const myState = React.restoreState("my-state", "hello"); @@ -213,44 +215,60 @@ document.body.append( ``` # Changelog + ## 1.1.0 -- Improve code -- Add missing `break` statements for directives -- Add `toggle` directive -- Improve documentation + +- Improve code +- Add missing `break` statements for directives +- Add `toggle` directive +- Improve documentation ## 1.1.1 -- Improve documentation + +- Improve documentation ## 1.1.2 -- Remove `console.log()` calls -- Improve documentation + +- Remove `console.log()` calls +- Improve documentation ## 1.1.3 -- Add `on:enter` directive + +- Add `on:enter` directive ## 1.2.0 + **BREAKING CHANGES** -- Replace UUID class with function; - - `new UUID()` => `UUID() returns string` -- Replace definition of `Identifiable` - - now has `id: string` instead of `uuid: UUID()` + +- Replace UUID class with function; + - `new UUID()` => `UUID() returns string` +- Replace definition of `Identifiable` + - now has `id: string` instead of `uuid: UUID()` Other changes: -- Add State persistence -- Add error description when utilizing `subscribe:children` incorrectly -- Fix bug where ListState subscriptions were not called -- Improve documentation + +- Add State persistence +- Add error description when utilizing `subscribe:children` incorrectly +- Fix bug where ListState subscriptions were not called +- Improve documentation ## 1.2.1 -- Add `set:` directive + +- Add `set:` directive ## 1.2.2 -- Remove list parameter from `ListItemConverter` -- Add `clear()` method to `ListState` + +- Remove list parameter from `ListItemConverter` +- Add `clear()` method to `ListState` ## 1.2.3 -- Allow `toggle:` to be used without a state + +- Allow `toggle:` to be used without a state ## 1.2.4 -- On `subscribe:children`, scroll new elements into view \ No newline at end of file + +- On `subscribe:children`, scroll new elements into view + +## 1.2.5 + +- Instead of using `scrollIntoView()`, `subscribe:children` now scrolls to the bottom \ No newline at end of file diff --git a/static/node_modules/bloatless-react/index.ts b/static/node_modules/bloatless-react/index.ts index 76f5ca0..4a4085a 100755 --- a/static/node_modules/bloatless-react/index.ts +++ b/static/node_modules/bloatless-react/index.ts @@ -205,19 +205,16 @@ export function createElement( try { const [listState, toElement] = value as [ listState: ListState, - ( - item: any, - listState: ListState - ) => HTMLElement + ListItemConverter ]; listState.handleAddition((newItem) => { - const child = toElement(newItem, listState); + const child = toElement(newItem); listState.handleRemoval(newItem, () => child.remove() ); element.append(child); - child.scrollIntoView(); + element.scrollTop = element.scrollHeight; }); } 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`; diff --git a/static/node_modules/bloatless-react/package.json b/static/node_modules/bloatless-react/package.json index 2746ccd..1e84a58 100644 --- a/static/node_modules/bloatless-react/package.json +++ b/static/node_modules/bloatless-react/package.json @@ -1,6 +1,6 @@ { "name": "bloatless-react", - "version": "1.2.4", + "version": "1.2.5-beta1", "description": "very minimal and flexible react alternative", "repository": { "type": "git", diff --git a/static/node_modules/udn-frontend/README.md b/static/node_modules/udn-frontend/README.md index c4875ac..25eeb78 100644 --- a/static/node_modules/udn-frontend/README.md +++ b/static/node_modules/udn-frontend/README.md @@ -35,10 +35,18 @@ UDN.unsubscribe("my-channel"); ```TypeScript export interface Message { + // connecting to server + requestingServerConnection?: boolean; + + // mailbox + requestingMailboxSetup?: boolean; + assignedMailboxId?: string; // sent by server + requestedMailbox?: string; + // subscribing to channel subscribeChannel?: string; unsubscribeChannel?: string; - subscribed?: boolean; // sent by the server to confirm subscription + subscribed?: boolean; // sent by server to confirm subscription //sending message messageChannel?: string; @@ -46,7 +54,7 @@ export interface Message { } export default class UDNFrontend { - private ws: WebSocket | undefined; + ws: WebSocket | undefined; // handlers private connectionHandler = () => {}; @@ -60,6 +68,7 @@ export default class UDNFrontend { // methods connect(address: string): void; + disconnect(address: string): void; sendMessage(channel: string, body: string): boolean; // true if message is sent 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 @@ -75,3 +84,11 @@ export default class UDNFrontend { ## 1.0.2 - minor fixes + +## 1.0.3 + +- add `disconnect()` method + +## 1.0.4 + +- make ws public \ No newline at end of file diff --git a/static/node_modules/udn-frontend/index.ts b/static/node_modules/udn-frontend/index.ts index dd5bce3..fbe2cdb 100644 --- a/static/node_modules/udn-frontend/index.ts +++ b/static/node_modules/udn-frontend/index.ts @@ -1,8 +1,16 @@ export interface Message { + // connecting to server + requestingServerConnection?: boolean; + + // mailbox + requestingMailboxSetup?: boolean; + assignedMailboxId?: string; // sent by server + requestedMailbox?: string; + // subscribing to channel subscribeChannel?: string; unsubscribeChannel?: string; - subscribed?: boolean; // sent by the server to confirm subscription + subscribed?: boolean; // sent by server to confirm subscription //sending message messageChannel?: string; @@ -10,12 +18,13 @@ export interface Message { } export default class UDNFrontend { - private ws: WebSocket | undefined; + ws: WebSocket | undefined; // handlers private connectionHandler = () => {}; private disconnectionHandler = () => {}; private messageHandler = (data: Message) => {}; + private mailboxHandler = (mailboxId: string) => {}; // init set onconnect(handler: () => void) { @@ -27,9 +36,12 @@ export default class UDNFrontend { set onmessage(handler: (data: Message) => void) { this.messageHandler = handler; } + set onmailbox(handler: (mailboxId: string) => void) { + this.mailboxHandler = handler; + } // utility methods - private send(messageObject: Object): boolean { + private send(messageObject: Message): boolean { if (this.ws == undefined) return false; const messageString = JSON.stringify(messageObject); @@ -39,16 +51,27 @@ export default class UDNFrontend { // public methods connect(address: string): void { + this.disconnect(); + this.ws = new WebSocket(address); this.ws.addEventListener("open", this.connectionHandler); this.ws.addEventListener("close", this.disconnectionHandler); this.ws.addEventListener("message", (message) => { const dataString = message.data.toString(); const data = JSON.parse(dataString); - this.messageHandler(data); + + if (data.assignedMailboxId) { + return this.mailboxHandler(data.assignedMailboxId); + } else { + this.messageHandler(data); + } }); } + disconnect(): void { + this.ws?.close(); + } + sendMessage(channel: string, body: string): boolean { const messageObject = { messageChannel: channel, @@ -70,4 +93,18 @@ export default class UDNFrontend { }; 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); + } } diff --git a/static/node_modules/udn-frontend/package.json b/static/node_modules/udn-frontend/package.json index 54e8b86..9dccf63 100644 --- a/static/node_modules/udn-frontend/package.json +++ b/static/node_modules/udn-frontend/package.json @@ -1,6 +1,6 @@ { "name": "udn-frontend", - "version": "1.0.2", + "version": "1.0.4-beta2", "description": "TypeScript library for UDN apps", "main": "index.ts", "scripts": { diff --git a/static/package.json b/static/package.json index 2c9591b..7d04992 100644 --- a/static/package.json +++ b/static/package.json @@ -1,8 +1,8 @@ { "dependencies": { - "bloatless-react": "^1.2.4", + "bloatless-react": "1.2.5-beta1", "esbuild": "latest", - "udn-frontend": "^1.0.2" + "udn-frontend": "1.0.4-beta2" }, "scripts": { "build": "esbuild src/index.tsx --bundle --outdir=dist" diff --git a/static/src/mainScreen.tsx b/static/src/mainScreen.tsx index c99351c..69686a5 100644 --- a/static/src/mainScreen.tsx +++ b/static/src/mainScreen.tsx @@ -3,8 +3,10 @@ import * as React from "bloatless-react"; import { isMessageEmpty, lastReceivedMessage, + mailboxId, newMessageBody, newMessageChannel, + requestMailbox, sendMessage, subscribe, subscriptionChannel, @@ -28,6 +30,22 @@ export function MainScreen() {
+
+
+ {getText("mailbox")} +

+
+
+ +
+ +
+ +
+