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