553 lines
21 KiB
JavaScript
553 lines
21 KiB
JavaScript
(() => {
|
|
// 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)
|
|
))))
|
|
);
|
|
})();
|