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 6bdac98..584bcec 100755 Binary files a/static/bun.lockb and b/static/bun.lockb differ 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")} +

+
+
+ +
+ +
+ +
+