change mailbox behaviour to use reception confirmations

This commit is contained in:
marlon-erler
2026-03-31 22:02:13 +00:00
parent 959dc4d288
commit ec020e6358
21 changed files with 2772 additions and 2476 deletions
+2 -2
View File
@@ -147,8 +147,8 @@ As you can see, there are many ways to connect multiple servers and communicate
Anyone can develop apps for the UDN project. Examples of such apps are listed below. If you have developed an app for the UDN project and you want your app to be listed here, create an issue.
| App Name | Description | Links |
| -- | -- | -- |
| Comms | Messenger for UDN | [more info](https://github.com/marlon-erler/udn-comms/) - [install](https://udn-comms.onrender.com/)
| -------- | ----------------- | ---------------------------------------------------------------------------------------------------- |
| Comms | Messenger for UDN | [more info](https://github.com/marlon-erler/udn-comms/) - [install](https://udn-comms.onrender.com/) |
---
+2 -1
View File
@@ -119,11 +119,12 @@ async function main() {
// CLI
function updateCLI() {
return;
console.clear();
console.log(Colors.bold.bgWhite("UNIVERSAL DECENTRALIZED NETWORK"));
console.log(
Colors.yellow("relevant output is available in the 'logs' file")
Colors.yellow("relevant output is available in the 'logs' file"),
);
console.log();
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "udn",
"version": "1.0.0-beta14",
"version": "1.0.0-beta15",
"description": "A websocket-based infrastructure for decentralized networks ",
"bin": {
"start": "index.ts"
+341 -91
View File
@@ -73,8 +73,8 @@
};
function createProxyState(statesToSubscibe, fn) {
const proxyState = new State(fn());
statesToSubscibe.forEach(
(state) => state.subscribe(() => proxyState.value = fn())
statesToSubscibe.forEach((state) =>
state.subscribe(() => (proxyState.value = fn())),
);
return proxyState;
}
@@ -85,7 +85,9 @@
});
}
function restoreState(localStorageKey, initialStateValue) {
const storedString = localStorage.getItem(localStorageKey) ?? JSON.stringify(initialStateValue);
const storedString =
localStorage.getItem(localStorageKey) ??
JSON.stringify(initialStateValue);
const convertedValue = JSON.parse(storedString);
const state = new State(convertedValue);
persistState(localStorageKey, state);
@@ -120,9 +122,8 @@
const [listState, toElement] = value;
listState.handleAddition((newItem) => {
const child = toElement(newItem);
listState.handleRemoval(
newItem,
() => child.remove()
listState.handleRemoval(newItem, () =>
child.remove(),
);
element.append(child);
element.scrollTop = element.scrollHeight;
@@ -133,7 +134,8 @@
} else {
const state = value;
state.subscribe(
(newValue) => element[directiveValue] = newValue
(newValue) =>
(element[directiveValue] = newValue),
);
}
break;
@@ -141,19 +143,22 @@
case "bind": {
const state = value;
state.subscribe(
(newValue) => element[directiveValue] = newValue
(newValue) => (element[directiveValue] = newValue),
);
element.addEventListener(
"input",
() => state.value = element[directiveValue]
() => (state.value = element[directiveValue]),
);
break;
}
case "toggle": {
if (value.subscribe) {
const state = value;
state.subscribe(
(newValue) => element.toggleAttribute(directiveValue, newValue)
state.subscribe((newValue) =>
element.toggleAttribute(
directiveValue,
newValue,
),
);
} else {
element.toggleAttribute(directiveValue, value);
@@ -162,8 +167,8 @@
}
case "set": {
const state = value;
state.subscribe(
(newValue) => element.setAttribute(directiveValue, newValue)
state.subscribe((newValue) =>
element.setAttribute(directiveValue, newValue),
);
break;
}
@@ -179,18 +184,12 @@
var UDNFrontend = class {
ws;
// HANDLERS
connectionHandler = () => {
};
disconnectionHandler = () => {
};
messageHandler = (data) => {
};
mailboxHandler = (mailboxId2) => {
};
mailboxConnectionHandler = (mailboxId2) => {
};
mailboxDeleteHandler = (mailboxId2) => {
};
connectionHandler = () => {};
disconnectionHandler = () => {};
messageHandler = (data) => {};
mailboxHandler = (mailboxId2) => {};
mailboxConnectionHandler = (mailboxId2) => {};
mailboxDeleteHandler = (mailboxId2) => {};
// INIT
set onconnect(handler) {
this.connectionHandler = handler;
@@ -231,7 +230,9 @@
if (data.assignedMailboxId) {
return this.mailboxHandler(data.assignedMailboxId);
} else if (data.connectedMailboxId) {
return this.mailboxConnectionHandler(data.connectedMailboxId);
return this.mailboxConnectionHandler(
data.connectedMailboxId,
);
} else if (data.deletedMailbox) {
return this.mailboxDeleteHandler(data.deletedMailbox);
} else {
@@ -249,39 +250,39 @@
sendMessage(channel, body) {
const messageObject = {
messageChannel: channel,
messageBody: body
messageBody: body,
};
return this.send(messageObject);
}
// subscription
subscribe(channel) {
const messageObject = {
subscribeChannel: channel
subscribeChannel: channel,
};
return this.send(messageObject);
}
unsubscribe(channel) {
const messageObject = {
unsubscribeChannel: channel
unsubscribeChannel: channel,
};
return this.send(messageObject);
}
// mailbox
requestMailbox() {
const messageObject = {
requestingMailboxSetup: true
requestingMailboxSetup: true,
};
return this.send(messageObject);
}
connectMailbox(mailboxId2) {
const messageObject = {
requestedMailbox: mailboxId2
requestedMailbox: mailboxId2,
};
return this.send(messageObject);
}
deleteMailbox(mailboxId2) {
const messageObject = {
deletingMailbox: mailboxId2
deletingMailbox: mailboxId2,
};
return this.send(messageObject);
}
@@ -310,7 +311,7 @@
settings: "Settings",
subscribeToChannel: "Subscribe to channel",
subscribe: "Subscribe",
unsubscribe: "Unubscribe"
unsubscribe: "Unubscribe",
};
var allTranslations = {
en: staticTextEnglish,
@@ -336,7 +337,7 @@
settings: "Configuraci\xF3n",
subscribeToChannel: "Suscribirse a un canal",
subscribe: "Suscribirse",
unsubscribe: "Cancelar"
unsubscribe: "Cancelar",
},
de: {
mailbox: "Briefkasten",
@@ -360,8 +361,8 @@
settings: "Einstellungen",
subscribeToChannel: "Kanal beitreten",
subscribe: "Beitreten",
unsubscribe: "Verlassen"
}
unsubscribe: "Verlassen",
},
};
var language = navigator.language.substring(0, 2);
var translations = allTranslations[language];
@@ -377,18 +378,25 @@
var mailboxId = restoreState("mailbox-id", "");
var isMailboxDisonnected = new State(true);
var messages = new ListState();
var lastReceivedMessage = new State(
translations.noMessagesReceived
);
var lastReceivedMessage = new State(translations.noMessagesReceived);
var statHTMLString = new State("");
async function updateStats() {
const response = await fetch(
`${window.location.protocol}//${window.location.host}/stats`
`${window.location.protocol}//${window.location.host}/stats`,
);
const data = await response.json();
let html = "";
data.forEach((item) => {
const tile = /* @__PURE__ */ createElement("div", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "secondary flex width-100" }, item[0].toString()), /* @__PURE__ */ createElement("b", null, item[1].toString()));
const tile = /* @__PURE__ */ createElement(
"div",
{ class: "tile" },
/* @__PURE__ */ createElement(
"span",
{ class: "secondary flex width-100" },
item[0].toString(),
),
/* @__PURE__ */ createElement("b", null, item[1].toString()),
);
html += tile.outerHTML;
});
statHTMLString.value = html;
@@ -398,7 +406,7 @@
var newMessageBody = new State("");
var isMessageEmpty = createProxyState(
[newMessageChannel, newMessageBody],
() => newMessageChannel.value == "" || newMessageBody.value == ""
() => newMessageChannel.value == "" || newMessageBody.value == "",
);
var UDN = new UDNFrontend();
UDN.onconnect = () => {
@@ -434,7 +442,7 @@
};
function connect() {
UDN.connect(
`${window.location.protocol.replace("http", "ws")}//${window.location.host}`
`${window.location.protocol.replace("http", "ws")}//${window.location.host}`,
);
}
connect();
@@ -459,106 +467,348 @@
// src/infoScreen.tsx
function InfoScreen() {
return /* @__PURE__ */ createElement("article", { id: "info-screen" }, /* @__PURE__ */ createElement("header", null, translations.serverInfo, /* @__PURE__ */ createElement("span", null, /* @__PURE__ */ createElement("button", { "on:click": updateStats }, /* @__PURE__ */ createElement("span", { class: "icon" }, "refresh")))), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("div", { class: "flex-column gap", "subscribe:innerHTML": statHTMLString })));
return /* @__PURE__ */ createElement(
"article",
{ id: "info-screen" },
/* @__PURE__ */ createElement(
"header",
null,
translations.serverInfo,
/* @__PURE__ */ createElement(
"span",
null,
/* @__PURE__ */ createElement(
"button",
{ "on:click": updateStats },
/* @__PURE__ */ createElement(
"span",
{ class: "icon" },
"refresh",
),
),
),
),
/* @__PURE__ */ createElement(
"div",
null,
/* @__PURE__ */ createElement("div", {
class: "flex-column gap",
"subscribe:innerHTML": statHTMLString,
}),
),
);
}
// src/mainScreen.tsx
function MainScreen() {
return /* @__PURE__ */ createElement("article", { id: "main-screen" }, /* @__PURE__ */ createElement("header", null, translations.settings), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("div", { class: "tile width-input" }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("b", null, translations.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, translations.mailbox), /* @__PURE__ */ createElement("p", { class: "secondary", "toggle:hidden": isMailboxDisonnected }, translations.mailboxConnected))), /* @__PURE__ */ createElement("div", { class: "flex-row width-input justify-end" }, /* @__PURE__ */ createElement(
return /* @__PURE__ */ createElement(
"article",
{ id: "main-screen" },
/* @__PURE__ */ createElement(
"header",
null,
translations.settings,
),
/* @__PURE__ */ createElement(
"div",
null,
/* @__PURE__ */ createElement(
"div",
{ class: "tile width-input" },
/* @__PURE__ */ createElement(
"div",
null,
/* @__PURE__ */ createElement(
"b",
null,
translations.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,
translations.mailbox,
),
/* @__PURE__ */ createElement(
"p",
{
class: "secondary",
"toggle:hidden": isMailboxDisonnected,
},
translations.mailboxConnected,
),
),
),
/* @__PURE__ */ createElement(
"div",
{ class: "flex-row width-input justify-end" },
/* @__PURE__ */ createElement(
"button",
{
class: "danger width-50",
"on:click": deleteMailbox,
"toggle:disabled": isMailboxDisonnected
"toggle:disabled": isMailboxDisonnected,
},
translations.deleteMailbox
), /* @__PURE__ */ createElement(
translations.deleteMailbox,
),
/* @__PURE__ */ createElement(
"button",
{
class: "primary width-50",
"on:click": requestMailbox,
"toggle:disabled": isDisconnected
"toggle:disabled": isDisconnected,
},
translations.requestMailbox,
/* @__PURE__ */ createElement("span", { class: "icon" }, "inbox")
)), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("label", { class: "tile" }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.subscribeToChannel), /* @__PURE__ */ createElement(
"input",
{
/* @__PURE__ */ createElement(
"span",
{ class: "icon" },
"inbox",
),
),
),
/* @__PURE__ */ createElement("hr", null),
/* @__PURE__ */ createElement(
"label",
{ class: "tile" },
/* @__PURE__ */ createElement(
"div",
null,
/* @__PURE__ */ createElement(
"span",
null,
translations.subscribeToChannel,
),
/* @__PURE__ */ createElement("input", {
placeholder: translations.channel_placeholder,
"bind:value": subscriptionChannel,
"on:enter": subscribe
}
))), /* @__PURE__ */ createElement("div", { class: "flex-row width-input justify-end" }, /* @__PURE__ */ createElement("button", { class: "danger width-50", "on:click": unsubscribe }, translations.unsubscribe), /* @__PURE__ */ createElement("button", { class: "primary width-50", "on:click": subscribe }, translations.subscribe, /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_forward"))), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("label", { class: "tile" }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.channel), /* @__PURE__ */ createElement(
"input",
{
"on:enter": subscribe,
}),
),
),
/* @__PURE__ */ createElement(
"div",
{ class: "flex-row width-input justify-end" },
/* @__PURE__ */ createElement(
"button",
{ class: "danger width-50", "on:click": unsubscribe },
translations.unsubscribe,
),
/* @__PURE__ */ createElement(
"button",
{ class: "primary width-50", "on:click": subscribe },
translations.subscribe,
/* @__PURE__ */ createElement(
"span",
{ class: "icon" },
"arrow_forward",
),
),
),
/* @__PURE__ */ createElement("hr", null),
/* @__PURE__ */ createElement(
"label",
{ class: "tile" },
/* @__PURE__ */ createElement(
"div",
null,
/* @__PURE__ */ createElement(
"span",
null,
translations.channel,
),
/* @__PURE__ */ createElement("input", {
placeholder: translations.channel_placeholder,
"bind:value": newMessageChannel,
"on:enter": sendMessage
}
))), /* @__PURE__ */ createElement("label", { class: "tile" }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.message), /* @__PURE__ */ createElement(
"input",
{
"on:enter": sendMessage,
}),
),
),
/* @__PURE__ */ createElement(
"label",
{ class: "tile" },
/* @__PURE__ */ createElement(
"div",
null,
/* @__PURE__ */ createElement(
"span",
null,
translations.message,
),
/* @__PURE__ */ createElement("input", {
placeholder: translations.message_placeholder,
"bind:value": newMessageBody,
"on:enter": sendMessage
}
))), /* @__PURE__ */ createElement("div", { class: "flex-row width-input justify-end" }, /* @__PURE__ */ createElement(
"on:enter": sendMessage,
}),
),
),
/* @__PURE__ */ createElement(
"div",
{ class: "flex-row width-input justify-end" },
/* @__PURE__ */ createElement(
"button",
{
class: "primary width-50",
"on:click": sendMessage,
"toggle:disabled": isMessageEmpty
"toggle:disabled": isMessageEmpty,
},
translations.send,
/* @__PURE__ */ createElement("span", { class: "icon" }, "send")
))));
/* @__PURE__ */ createElement(
"span",
{ class: "icon" },
"send",
),
),
),
),
);
}
// src/messageScreen.tsx
var convertMessageToElement = (message) => {
return /* @__PURE__ */ createElement("div", { class: "tile", style: "flex: 0" }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", { class: "secondary" }, message.channel), /* @__PURE__ */ createElement("b", null, message.body)));
return /* @__PURE__ */ createElement(
"div",
{ class: "tile", style: "flex: 0" },
/* @__PURE__ */ createElement(
"div",
null,
/* @__PURE__ */ createElement(
"span",
{ class: "secondary" },
message.channel,
),
/* @__PURE__ */ createElement("b", null, message.body),
),
);
};
function MessageScreen() {
return /* @__PURE__ */ createElement("article", { id: "message-screen" }, /* @__PURE__ */ createElement("header", null, translations.messagesReceived), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement(
return /* @__PURE__ */ createElement(
"article",
{ id: "message-screen" },
/* @__PURE__ */ createElement(
"header",
null,
translations.messagesReceived,
),
/* @__PURE__ */ createElement(
"div",
{
null,
/* @__PURE__ */ createElement("div", {
class: "flex-column gap",
"subscribe:children": [messages, convertMessageToElement]
}
)), /* @__PURE__ */ createElement("footer", null, /* @__PURE__ */ createElement(
"input",
{
"subscribe:children": [messages, convertMessageToElement],
}),
),
/* @__PURE__ */ createElement(
"footer",
null,
/* @__PURE__ */ createElement("input", {
style: "max-width: unset",
placeholder: translations.message_placeholder_generic,
"bind:value": newMessageBody,
"on:enter": sendMessage
}
), /* @__PURE__ */ createElement(
"on:enter": sendMessage,
}),
/* @__PURE__ */ createElement(
"button",
{
class: "primary",
"on:click": sendMessage,
"toggle:disabled": isMessageEmpty
"toggle:disabled": isMessageEmpty,
},
/* @__PURE__ */ createElement("span", { class: "icon" }, "send")
)));
/* @__PURE__ */ createElement(
"span",
{ class: "icon" },
"send",
),
),
),
);
}
// src/index.tsx
document.body.prepend(
/* @__PURE__ */ createElement("menu", null, /* @__PURE__ */ createElement("a", { class: "tab-link", href: "#info-screen", active: true }, /* @__PURE__ */ createElement("span", { class: "icon" }, "info"), translations.info), /* @__PURE__ */ createElement("a", { class: "tab-link", href: "#main-screen" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "settings"), translations.settings), /* @__PURE__ */ createElement("a", { class: "tab-link", href: "#message-screen" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "chat"), translations.messages))
/* @__PURE__ */ createElement(
"menu",
null,
/* @__PURE__ */ createElement(
"a",
{ class: "tab-link", href: "#info-screen", active: true },
/* @__PURE__ */ createElement(
"span",
{ class: "icon" },
"info",
),
translations.info,
),
/* @__PURE__ */ createElement(
"a",
{ class: "tab-link", href: "#main-screen" },
/* @__PURE__ */ createElement(
"span",
{ class: "icon" },
"settings",
),
translations.settings,
),
/* @__PURE__ */ createElement(
"a",
{ class: "tab-link", href: "#message-screen" },
/* @__PURE__ */ createElement(
"span",
{ class: "icon" },
"chat",
),
translations.messages,
),
),
);
document.querySelector("main").append(InfoScreen(), MainScreen(), MessageScreen());
document
.querySelector("main")
.append(InfoScreen(), MainScreen(), MessageScreen());
document.body.append(
/* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": isDisconnected }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement(
/* @__PURE__ */ createElement(
"div",
{ class: "modal", "toggle:open": isDisconnected },
/* @__PURE__ */ createElement(
"div",
null,
/* @__PURE__ */ createElement(
"main",
null,
/* @__PURE__ */ createElement(
"div",
{
class: "flex-column align-center justify-center width-100 height-100",
style: "gap: 1rem"
style: "gap: 1rem",
},
/* @__PURE__ */ createElement("span", { class: "icon error", style: "font-size: 3rem" }, "signal_disconnected"),
/* @__PURE__ */ createElement("h1", { class: "error" }, translations.disconnected),
/* @__PURE__ */ createElement("p", { class: "secondary" }, translations.reconnecting)
))))
/* @__PURE__ */ createElement(
"span",
{ class: "icon error", style: "font-size: 3rem" },
"signal_disconnected",
),
/* @__PURE__ */ createElement(
"h1",
{ class: "error" },
translations.disconnected,
),
/* @__PURE__ */ createElement(
"p",
{ class: "secondary" },
translations.reconnecting,
),
),
),
),
),
);
})();
+8 -3
View File
@@ -245,7 +245,10 @@ SIZES
--safe-area-top: env(safe-area-inset-top);
--safe-area-left: env(safe-area-inset-left);
--safe-area-right: env(safe-area-inset-right);
--safe-area-bottom: max(env(safe-area-inset-bottom), var(--control-padding));
--safe-area-bottom: max(
env(safe-area-inset-bottom),
var(--control-padding)
);
}
/*
@@ -593,7 +596,8 @@ label.tile:has(> span.icon) input {
article > div {
padding-bottom: calc(
var(--menu-height-mobile) + var(--control-height) + var(--content-padding)
var(--menu-height-mobile) + var(--control-height) +
var(--content-padding)
);
}
@@ -909,7 +913,8 @@ menu {
article > div {
padding-bottom: calc(
var(--menu-height-mobile) + var(--header-height) + var(--content-padding)
var(--menu-height-mobile) + var(--header-height) +
var(--content-padding)
);
}
+5 -2
View File
@@ -14,9 +14,12 @@ document.querySelector("main").addEventListener("scroll", () => {
async function registerServiceWorker() {
if ("serviceWorker" in navigator) {
try {
const registration = await navigator.serviceWorker.register("/sw.js", {
const registration = await navigator.serviceWorker.register(
"/sw.js",
{
scope: "/",
});
},
);
if (registration.installing) {
setTimeout(() => window.location.reload(), 500);
} else if (registration.active) {
+2 -2
View File
@@ -20,7 +20,7 @@ document.body.prepend(
<span class="icon">chat</span>
{translations.messages}
</a>
</menu>
</menu>,
);
document
@@ -43,5 +43,5 @@ document.body.append(
</div>
</main>
</div>
</div>
</div>,
);
+4 -1
View File
@@ -16,7 +16,10 @@ export function InfoScreen() {
</span>
</header>
<div>
<div class="flex-column gap" subscribe:innerHTML={statHTMLString}></div>
<div
class="flex-column gap"
subscribe:innerHTML={statHTMLString}
></div>
</div>
</article>
);
+8 -2
View File
@@ -26,7 +26,10 @@ export function MainScreen() {
<div class="tile width-input">
<div>
<b>{translations.messageLastReceived}</b>
<p class="secondary" subscribe:innerText={lastReceivedMessage}></p>
<p
class="secondary"
subscribe:innerText={lastReceivedMessage}
></p>
</div>
</div>
@@ -35,7 +38,10 @@ export function MainScreen() {
<div class="tile width-input">
<div>
<b>{translations.mailbox}</b>
<p class="secondary" toggle:hidden={isMailboxDisonnected}>
<p
class="secondary"
toggle:hidden={isMailboxDisonnected}
>
{translations.mailboxConnected}
</p>
</div>
+12 -7
View File
@@ -4,7 +4,10 @@ import UDNFrontend from "udn-frontend";
import { translations } from "./translations";
export class Message {
constructor(public channel: string, public body: string) {}
constructor(
public channel: string,
public body: string,
) {}
}
// STATE
@@ -18,14 +21,14 @@ export const isMailboxDisonnected = new React.State(true);
// messages
export const messages = new React.ListState<Message>();
export const lastReceivedMessage = new React.State(
translations.noMessagesReceived
translations.noMessagesReceived,
);
// stats
export const statHTMLString = new React.State<string>("");
export async function updateStats() {
const response = await fetch(
`${window.location.protocol}//${window.location.host}/stats`
`${window.location.protocol}//${window.location.host}/stats`,
);
const data: [string, string][] = await response.json();
@@ -33,7 +36,9 @@ export async function updateStats() {
data.forEach((item) => {
const tile = (
<div class="tile">
<span class="secondary flex width-100">{item[0].toString()}</span>
<span class="secondary flex width-100">
{item[0].toString()}
</span>
<b>{item[1].toString()}</b>
</div>
);
@@ -50,7 +55,7 @@ export const newMessageBody = new React.State("");
export const isMessageEmpty = React.createProxyState(
[newMessageChannel, newMessageBody],
() => newMessageChannel.value == "" || newMessageBody.value == ""
() => newMessageChannel.value == "" || newMessageBody.value == "",
);
// UDN Frontend
@@ -87,7 +92,7 @@ UDN.onmailboxconnect = () => {
UDN.onmailboxdelete = () => {
isMailboxDisonnected.value = true;
}
};
UDN.ondisconnect = () => {
isDisconnected.value = true;
@@ -97,7 +102,7 @@ UDN.ondisconnect = () => {
function connect() {
UDN.connect(
`${window.location.protocol.replace("http", "ws")}//${window.location.host}`
`${window.location.protocol.replace("http", "ws")}//${window.location.host}`,
);
}
connect();
+17 -11
View File
@@ -8,7 +8,7 @@ import Crypto from "crypto";
import { WS } from "./index";
export interface Subscriber {
send: (message: string) => void;
send: (message: string, compress?: boolean, id?: string) => void;
}
export class Mailbox implements Subscriber {
@@ -35,7 +35,7 @@ export class Mailbox implements Subscriber {
{
assignedMailboxId: this.id,
},
ws
ws,
);
}
@@ -46,7 +46,7 @@ export class Mailbox implements Subscriber {
{
connectedMailboxId: this.id,
},
this.ws
this.ws,
);
}
@@ -57,7 +57,7 @@ export class Mailbox implements Subscriber {
}
// storage
messages = new Set<string>();
messages = new Map<string, string>();
// websocket
private _ws: WS | undefined;
@@ -90,13 +90,13 @@ export class Mailbox implements Subscriber {
}
// sending
send(message: string): void {
if (this._ws?.readyState == 1) {
this._ws.send(message);
this.dateLastUsed = new Date();
} else {
this.messages.add(message);
}
send(message: string, _?: boolean, uuid?: string): void {
this._ws?.send(message);
console.log(this.id, uuid, message, "sending");
if (!uuid) return;
this.messages.set(uuid, message);
}
sendUnreadMessages(): void {
@@ -107,6 +107,12 @@ export class Mailbox implements Subscriber {
this.messages.delete(message);
});
}
checkMessageReceivedConfirmation(uuid: string): void {
if (!this.messages.has(uuid)) return;
this.dateLastUsed = new Date();
this.messages.delete(uuid);
}
}
export class SubscriptionMap {
+5 -5
View File
@@ -3,9 +3,7 @@ import { PACKAGE_FILE, STATIC_DIR, WS } from ".";
import Colors from "colors";
import Fs from "fs/promises";
import Path from "path";
import {
WebSocketMessage,
} from "./websocket-handler";
import { WebSocketMessage } from "./websocket-handler";
// CONFIG
const STAT_INDENT = 25;
@@ -39,12 +37,14 @@ async function writeToLogFile(message: string): Promise<void> {
// stats
export function writeStatString(label: string, value: string): void {
console.log(`${label.padEnd(STAT_INDENT, " ")} ${Colors.bold.green(value)}`);
console.log(
`${label.padEnd(STAT_INDENT, " ")} ${Colors.bold.green(value)}`,
);
}
export function writeStatNumber(label: string, value: number): void {
console.log(
`${label.padEnd(STAT_INDENT, " ")} ${Colors.bold.blue(value.toString())}`
`${label.padEnd(STAT_INDENT, " ")} ${Colors.bold.blue(value.toString())}`,
);
}
+28 -11
View File
@@ -34,6 +34,9 @@ export interface WebSocketMessage {
//sending message
messageChannel?: string;
messageBody?: string;
// confirming message
confirmingMessageReceived?: boolean;
}
// TRACKING
@@ -64,7 +67,7 @@ export function forgetConnection(ws: WS): void {
export function trackSubscription(
subscriber: Subscriber,
channel: string
channel: string,
): void {
subscriptionMap.set(subscriber, channel);
confirmSubscription(subscriber, channel);
@@ -72,7 +75,7 @@ export function trackSubscription(
export function forgetSubscription(
subscriber: Subscriber,
channel: string
channel: string,
): void {
subscriptionMap.delete(subscriber, channel);
confirmSubscription(subscriber, channel);
@@ -104,7 +107,7 @@ export function reconnectServers(config: typeof defaultConfig): void {
export function connectServer(
address: string,
config: typeof defaultConfig
config: typeof defaultConfig,
): void {
try {
// connect
@@ -153,7 +156,7 @@ export function subscribeChannel(server: WS, channel: string): void {
export function processMessage(
ws: WS,
messageString: string,
config: typeof defaultConfig
config: typeof defaultConfig,
): void {
// MESSAGE ID
// check id
@@ -161,6 +164,14 @@ export function processMessage(
if (!messageObject.uuid) {
// assign if no id
messageObject.uuid = Crypto.randomUUID();
} else if (messageObject.confirmingMessageReceived == true) {
// received confirmation
mailboxes.forEach((mailbox) => {
if (mailbox.ws != ws) return;
return mailbox.checkMessageReceivedConfirmation(
messageObject.uuid!,
);
});
} else {
// ignore if already seen
if (knownMessageIds.has(messageObject.uuid)) return;
@@ -179,7 +190,11 @@ export function processMessage(
// MESSAGE
doIfIsString(messageObject.messageChannel, (messageChannel) => {
if (messageObject.messageBody == undefined) return;
sendMessage(messageChannel, messageObject.messageBody, messageObject.uuid!);
sendMessage(
messageChannel,
messageObject.messageBody,
messageObject.uuid!,
);
});
// MAILBOX
@@ -192,7 +207,7 @@ export function processMessage(
if (!mailbox)
return sendWebSocketMessage(
{ deletedMailbox: requestedMailbox },
ws
ws,
);
mailbox.ws = ws;
});
@@ -225,16 +240,16 @@ export function removeMailbox(mailbox: Mailbox) {
// MESSAGING
export function sendWebSocketMessage(
messageObject: WebSocketMessage,
destination: Subscriber
destination: Subscriber,
): void {
const messageString = JSON.stringify(messageObject);
destination.send(messageString);
destination.send(messageString, false, messageObject.uuid);
}
export function sendMessage(
messageChannel: string,
messageBody: string,
uuid: string
uuid: string,
): void {
const channels = messageChannel.split("/");
subscriptionMap.forwardMessage(channels, {
@@ -254,11 +269,13 @@ function requestServerConnection(ws: WS): void {
function confirmSubscription(
subscriber: Subscriber,
messageChannel: string
messageChannel: string,
): void {
const messageObject: WebSocketMessage = {
messageChannel,
subscribed: subscriptionMap.getChannelList(subscriber)?.has(messageChannel)
subscribed: subscriptionMap
.getChannelList(subscriber)
?.has(messageChannel)
? true
: false,
};