(() => { // static/node_modules/bloatless-react/index.ts var State = class { _value; _bindings = /* @__PURE__ */ new Set(); // init constructor(initialValue) { this._value = initialValue; } // value get value() { return this._value; } set value(newValue) { this._value = newValue; this.callSubscriptions(); } // subscriptions callSubscriptions() { this._bindings.forEach((fn) => fn(this._value)); } subscribe(fn) { this._bindings.add(fn); fn(this._value); } subscribeSilent(fn) { this._bindings.add(fn); } // stringify toString() { return JSON.stringify(this._value); } }; var ListState = class extends State { additionHandlers = /* @__PURE__ */ new Set(); removalHandlers = /* @__PURE__ */ new Map(); genericRemovalHandlers = /* @__PURE__ */ new Set(); // init constructor(initialItems) { super(new Set(initialItems)); } // list add(...items) { items.forEach((item) => { if (this.value.has(item)) return; this.value.add(item); this.additionHandlers.forEach((handler) => handler(item)); }); this.callSubscriptions(); } remove(...items) { items.forEach((item) => { this.value.delete(item); this.genericRemovalHandlers.forEach((handler) => handler(item)); if (!this.removalHandlers.has(item)) return; this.removalHandlers.get(item).forEach((handler) => handler(item)); this.removalHandlers.delete(item); }); this.callSubscriptions(); } clear() { this.remove(...this.value.values()); } // handlers handleAddition(handler) { this.additionHandlers.add(handler); [...this.value.values()].forEach(handler); } handleRemoval(item, handler) { if (!this.removalHandlers.has(item)) this.removalHandlers.set(item, /* @__PURE__ */ new Set()); this.removalHandlers.get(item).add(handler); } handleRemovals(handler) { this.genericRemovalHandlers.add(handler); } // stringification toString() { const array = [...this.value.values()]; const json = JSON.stringify(array); return json; } }; function createProxyState(statesToSubscibe, fn) { const proxyState = new State(fn()); statesToSubscibe.forEach( (state) => state.subscribeSilent(() => proxyState.value = fn()) ); 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) Object.entries(attributes).forEach((entry) => { const [attributename, value] = entry; const [directiveKey, directiveValue] = attributename.split(":"); switch (directiveKey) { case "on": { switch (directiveValue) { case "enter": { element.addEventListener("keydown", (e) => { if (e.key != "Enter") return; value(e); }); break; } default: { element.addEventListener(directiveValue, value); } } break; } case "keystroke": { element.addEventListener("keydown", (e) => { if (e.metaKey == false && e.ctrlKey == false) return; if (e.key != directiveValue) return; value(e); }); break; } case "subscribe": { const state = value; state.subscribe( (newValue) => element[directiveValue] = newValue ); break; } case "bind": { const state = value; state.subscribe( (newValue) => element[directiveValue] = newValue ); element.addEventListener( "input", () => state.value = element[directiveValue] ); break; } case "toggle": { if (value.subscribe) { const state = value; state.subscribe( (newValue) => element.toggleAttribute(directiveValue, newValue) ); } else { element.toggleAttribute(directiveValue, value); } break; } case "set": { const state = value; state.subscribe( (newValue) => element.setAttribute(directiveValue, newValue) ); break; } case "children": { switch (directiveValue) { case "set": { const state = value; state.subscribe((newValue) => { element.innerHTML = ""; element.append(...[newValue].flat()); }); break; } case "append": case "prepend": { try { const [listState, toElement] = value; listState.handleAddition((newItem) => { const child = toElement(newItem); listState.handleRemoval( newItem, () => child.remove() ); if (directiveValue == "append") { element.append(child); } else if (directiveValue == "prepend") { element.prepend(child); } }); } catch (error) { console.error(error); throw `error: cannot process subscribe:children directive. Usage: "children:append={[list, converter]}"; you can find a more detailed example in the documentation.`; } } } break; } default: element.setAttribute(attributename, value); } }); children.filter((x) => x).forEach((child) => element.append(child)); return element; } // static/node_modules/udn-frontend/index.ts var UDNFrontend = class { ws; // HANDLERS connectionHandler = () => { }; disconnectionHandler = () => { }; messageHandler = (data) => { }; mailboxHandler = (mailboxId2) => { }; mailboxConnectionHandler = (mailboxId2) => { }; mailboxDeleteHandler = (mailboxId2) => { }; // INIT set onconnect(handler) { this.connectionHandler = handler; } set ondisconnect(handler) { this.disconnectionHandler = handler; } set onmessage(handler) { this.messageHandler = handler; } set onmailboxcreate(handler) { this.mailboxHandler = handler; } set onmailboxconnect(handler) { this.mailboxConnectionHandler = handler; } set onmailboxdelete(handler) { this.mailboxDeleteHandler = handler; } // UTILITY METHODS send(messageObject) { if (this.ws == void 0) return false; if (this.ws.readyState != 1) return false; const messageString = JSON.stringify(messageObject); this.ws.send(messageString); return true; } // PUBLIC METHODS // connection connect(address) { try { 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); if (data.assignedMailboxId) { return this.mailboxHandler(data.assignedMailboxId); } else if (data.connectedMailboxId) { return this.mailboxConnectionHandler(data.connectedMailboxId); } else if (data.deletedMailbox) { return this.mailboxDeleteHandler(data.deletedMailbox); } else { this.send({ uuid: data.uuid, confirmingMessageReceived: true }); this.messageHandler(data); } }); } catch (error) { console.error(error); } } disconnect() { this.ws?.close(); } // message sendMessage(channel, body) { const messageObject = { messageChannel: channel, messageBody: body }; return this.send(messageObject); } // subscription subscribe(channel) { const messageObject = { subscribeChannel: channel }; return this.send(messageObject); } unsubscribe(channel) { const messageObject = { unsubscribeChannel: channel }; return this.send(messageObject); } // mailbox requestMailbox() { const messageObject = { requestingMailboxSetup: true }; return this.send(messageObject); } connectMailbox(mailboxId2) { const messageObject = { requestedMailbox: mailboxId2 }; return this.send(messageObject); } deleteMailbox(mailboxId2) { const messageObject = { deletingMailbox: mailboxId2 }; return this.send(messageObject); } }; // static/src/translations.tsx var translations = { mailbox: "Mailbox", requestMailbox: "Request", deleteMailbox: "Delete", mailboxConnected: "Mailbox active", channel: "Channel", channel_placeholder: "my-channel", disconnected: "Server disconnected", reconnecting: "Reconnecting...", info: "Info", message: "Message", message_placeholder: "Hello, world!", noMessagesReceived: "No messages received", messageLastReceived: "Last received message", serverInfo: "Server Info", settings: "Settings", subscribeToChannel: "Subscribe to channel", subscribe: "Subscribe", unsubscribe: "Unubscribe", privacyPolicy: "Privacy Policy", privacyPolicyContent: [ "Data retention: This server may store messages sent for up to seven days if the recipient has mailboxes enabled in order to receive messages sent to them while they are offline. Mailboxes are deleted after seven days of inactivity, or upon user request. Messages are removed from mailboxes once received. Data stored about messages consists of the channel which the message is sent on, and the message body (content).", "Processing: This server does not implement any kind of authentication or other means to identify, track, or verify user identity. The administration of this server has no means to track user activity or access data in any way not accessible to other non-administrative users. However, IP addresses are processed in order to uphold connections between client devices and this server (Legal basis: Art. 6(1)(b) GDPR \u2013 provision of the requested communication service)", "Security: Data sent to this server is broadcasted on channels. Anyone may receive any message if they know the corresponding channel. It is strongly recommended to encrypt messages before sending them to this server (end-to-end encryption).", "Rights: Pursuant to Art. 11 GDPR, because the administration does not retain records identifying the authors of individual transmissions, individual rights of access or erasure regarding specific messages cannot be fulfilled. Users have the right to lodge a complaint with a competent data protection authority (Art. 77 GDPR)." ], imprint: "Imprint" }; // static/src/model.tsx var Message = class { constructor(channel, body) { this.channel = channel; this.body = body; } }; var isDisconnected = new State(true); var mailboxId = restoreState("mailbox-id", ""); var isMailboxDisonnected = new State(true); var messages = new ListState(); var lastReceivedMessage = new State( translations.noMessagesReceived ); var statHTMLString = new State(""); async function updateStats() { const response = await fetch( `${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("div", { class: "flex-column align-start" }, /* @__PURE__ */ createElement("span", { class: "secondary flex width-100" }, item[0].toString()), ...item[1].toString().split("\n").map((string) => /* @__PURE__ */ createElement("b", null, string)))); html += tile.outerHTML; }); statHTMLString.value = html; } var subscriptionChannel = new State(""); var newMessageChannel = new State(""); var newMessageBody = new State(""); var isMessageEmpty = createProxyState( [newMessageChannel, newMessageBody], () => newMessageChannel.value == "" || newMessageBody.value == "" ); var UDN = new UDNFrontend(); UDN.onconnect = () => { isDisconnected.value = false; updateStats(); if (mailboxId.value != "") { UDN.connectMailbox(mailboxId.value); } }; UDN.onmessage = (data) => { lastReceivedMessage.value = JSON.stringify(data, null, 4); const { messageChannel, messageBody } = data; if (messageChannel && messageBody) { const messageObject = new Message(messageChannel, messageBody); if (messageBody) messages.add(messageObject); } }; UDN.onmailboxcreate = (id) => { console.log(id); mailboxId.value = id; UDN.connectMailbox(id); }; UDN.onmailboxconnect = () => { isMailboxDisonnected.value = false; }; UDN.onmailboxdelete = () => { isMailboxDisonnected.value = true; }; UDN.ondisconnect = () => { isDisconnected.value = true; isMailboxDisonnected.value = true; setTimeout(connect, 1e3); }; function connect() { UDN.connect( `${window.location.protocol.replace("http", "ws")}//${window.location.host}` ); } connect(); function subscribe() { UDN.subscribe(subscriptionChannel.value); } function unsubscribe() { UDN.unsubscribe(subscriptionChannel.value); } function sendMessage() { if (isMessageEmpty.value == true) return; UDN.sendMessage(newMessageChannel.value, newMessageBody.value); newMessageBody.value = ""; } function requestMailbox() { UDN.requestMailbox(); } function deleteMailbox() { UDN.deleteMailbox(mailboxId.value); } // static/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 } ))); } // static/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( "button", { class: "danger width-50", "on:click": deleteMailbox, "toggle:disabled": isMailboxDisonnected }, translations.deleteMailbox ), /* @__PURE__ */ createElement( "button", { class: "primary width-50", "on:click": requestMailbox, "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", { 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", { 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", { placeholder: translations.message_placeholder, "bind:value": newMessageBody, "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 }, translations.send, /* @__PURE__ */ createElement("span", { class: "icon" }, "send") )))); } // static/src/privacyScreen.tsx var specificPolicy = new ListState([]); fetch("/privacy").then(async (response) => { (await response.text()).split("\n").forEach((x) => specificPolicy.add(x)); }); function paragraph(x) { return /* @__PURE__ */ createElement("p", null, x); } function PrivacyScreen() { return /* @__PURE__ */ createElement("article", { id: "privacy-screen" }, /* @__PURE__ */ createElement("header", null, translations.privacyPolicy), /* @__PURE__ */ createElement("div", null, ...translations.privacyPolicyContent.map(paragraph), /* @__PURE__ */ createElement("div", { "children:append": [specificPolicy, paragraph] }))); } // static/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: "#privacy-screen" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "privacy_tip"), translations.privacyPolicy), /* @__PURE__ */ createElement("a", { class: "tab-link", href: "/imprint" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "attribution"), translations.imprint)) ); document.querySelector("main").append(InfoScreen(), MainScreen(), PrivacyScreen()); document.body.append( /* @__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" }, /* @__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) )))) ); })();