2024-07-04 16:11:02 +02:00
(() => {
2026-04-02 21:25:39 +00:00
// static/node_modules/bloatless-react/index.ts
var State = class {
_value ;
_bindings = /* @__PURE__ */ new Set ();
// init
constructor ( initialValue ) {
this . _value = initialValue ;
2024-07-08 13:24:21 +02:00
}
2026-04-02 21:25:39 +00:00
// value
get value () {
return this . _value ;
2024-07-05 00:07:39 +02:00
}
2026-04-02 21:25:39 +00:00
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 );
}
2026-10-12 20:38:04 +02:00
subscribeSilent ( fn ) {
this . _bindings . add ( fn );
}
2026-04-02 21:25:39 +00:00
// stringify
toString () {
return JSON . stringify ( this . _value );
}
};
var ListState = class extends State {
additionHandlers = /* @__PURE__ */ new Set ();
removalHandlers = /* @__PURE__ */ new Map ();
2026-10-12 20:38:04 +02:00
genericRemovalHandlers = /* @__PURE__ */ new Set ();
2026-04-02 21:25:39 +00:00
// init
constructor ( initialItems ) {
super ( new Set ( initialItems ));
}
// list
add (... items ) {
items . forEach (( item ) => {
2026-10-12 20:38:04 +02:00
if ( this . value . has ( item )) return ;
2026-04-02 21:25:39 +00:00
this . value . add ( item );
this . additionHandlers . forEach (( handler ) => handler ( item ));
});
this . callSubscriptions ();
}
remove (... items ) {
items . forEach (( item ) => {
this . value . delete ( item );
2026-10-12 20:38:04 +02:00
this . genericRemovalHandlers . forEach (( handler ) => handler ( item ));
2026-04-02 21:25:39 +00:00
if ( ! this . removalHandlers . has ( item )) return ;
2026-10-12 20:38:04 +02:00
this . removalHandlers . get ( item ). forEach (( handler ) => handler ( item ));
2026-04-02 21:25:39 +00:00
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 ) {
2026-10-12 20:38:04 +02:00
if ( ! this . removalHandlers . has ( item ))
this . removalHandlers . set ( item , /* @__PURE__ */ new Set ());
this . removalHandlers . get ( item ). add ( handler );
}
handleRemovals ( handler ) {
this . genericRemovalHandlers . add ( handler );
2026-04-02 21:25:39 +00:00
}
// stringification
toString () {
2026-10-12 20:38:04 +02:00
const array = [... this . value . values ()];
2026-04-02 21:25:39 +00:00
const json = JSON . stringify ( array );
return json ;
}
};
function createProxyState ( statesToSubscibe , fn ) {
const proxyState = new State ( fn ());
statesToSubscibe . forEach (
2026-10-12 20:38:04 +02:00
( state ) => state . subscribeSilent (() => proxyState . value = fn ())
2026-04-02 21:25:39 +00:00
);
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 ;
2026-10-12 20:38:04 +02:00
value ( e );
2024-07-08 13:24:21 +02:00
});
2026-04-02 21:25:39 +00:00
break ;
}
default : {
element . addEventListener ( directiveValue , value );
}
2024-07-08 13:24:21 +02:00
}
2026-04-02 21:25:39 +00:00
break ;
}
2026-10-12 20:38:04 +02:00
case "keystroke" : {
element . addEventListener ( "keydown" , ( e ) => {
if ( e . metaKey == false && e . ctrlKey == false ) return ;
if ( e . key != directiveValue ) return ;
value ( e );
});
break ;
}
2026-04-02 21:25:39 +00:00
case "subscribe" : {
2026-10-12 20:38:04 +02:00
const state = value ;
state . subscribe (
( newValue ) => element [ directiveValue ] = newValue
);
2026-04-02 21:25:39 +00:00
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 ;
}
2026-10-12 20:38:04 +02:00
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 ;
}
2026-04-02 21:25:39 +00:00
default :
element . setAttribute ( attributename , value );
2024-07-04 16:11:02 +02:00
}
2026-04-02 21:25:39 +00:00
});
children . filter (( x ) => x ). forEach (( child ) => element . append ( child ));
return element ;
}
2024-07-04 16:11:02 +02:00
2026-04-02 21:25:39 +00:00
// static/node_modules/udn-frontend/index.ts
var UDNFrontend = class {
ws ;
// HANDLERS
connectionHandler = () => {
2024-07-06 14:11:48 +02:00
};
2026-04-02 21:25:39 +00:00
disconnectionHandler = () => {
2024-07-08 21:13:56 +02:00
};
2026-04-02 21:25:39 +00:00
messageHandler = ( data ) => {
2024-07-08 23:34:16 +02:00
};
2026-04-02 21:25:39 +00:00
mailboxHandler = ( mailboxId2 ) => {
2024-07-09 02:36:43 +02:00
};
2026-04-02 21:25:39 +00:00
mailboxConnectionHandler = ( mailboxId2 ) => {
2026-03-31 22:02:13 +00:00
};
2026-04-02 21:25:39 +00:00
mailboxDeleteHandler = ( mailboxId2 ) => {
2026-03-31 22:02:13 +00:00
};
2026-04-02 21:25:39 +00:00
// INIT
set onconnect ( handler ) {
this . connectionHandler = handler ;
2024-07-08 21:13:56 +02:00
}
2026-04-02 21:25:39 +00:00
set ondisconnect ( handler ) {
this . disconnectionHandler = handler ;
2024-07-06 14:11:48 +02:00
}
2026-04-02 21:25:39 +00:00
set onmessage ( handler ) {
this . messageHandler = handler ;
2024-07-06 14:11:48 +02:00
}
2026-04-02 21:25:39 +00:00
set onmailboxcreate ( handler ) {
this . mailboxHandler = handler ;
2024-07-06 14:11:48 +02:00
}
2026-04-02 21:25:39 +00:00
set onmailboxconnect ( handler ) {
this . mailboxConnectionHandler = handler ;
2024-07-08 21:13:56 +02:00
}
2026-04-02 21:25:39 +00:00
set onmailboxdelete ( handler ) {
this . mailboxDeleteHandler = handler ;
2024-07-08 23:34:16 +02:00
}
2026-04-02 21:25:39 +00:00
// UTILITY METHODS
send ( messageObject ) {
if ( this . ws == void 0 ) return false ;
2026-10-12 20:38:04 +02:00
if ( this . ws . readyState != 1 ) return false ;
2026-04-02 21:25:39 +00:00
const messageString = JSON . stringify ( messageObject );
this . ws . send ( messageString );
return true ;
2024-07-08 21:13:56 +02:00
}
2026-04-02 21:25:39 +00:00
// 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 {
2026-10-12 20:38:04 +02:00
this . send ({ uuid : data . uuid , confirmingMessageReceived : true });
2026-04-02 21:25:39 +00:00
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 );
2024-07-05 00:07:39 +02:00
}
2026-04-02 21:25:39 +00:00
deleteMailbox ( mailboxId2 ) {
const messageObject = {
deletingMailbox : mailboxId2
};
return this . send ( messageObject );
}
};
2024-07-05 00:07:39 +02:00
2026-04-02 21:25:39 +00:00
// static/src/translations.tsx
2026-10-12 20:38:04 +02:00
var translations = {
2026-04-02 21:25:39 +00:00
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" ,
2026-10-12 20:38:04 +02:00
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"
2026-04-02 21:25:39 +00:00
};
2026-03-31 22:02:13 +00:00
2026-04-02 21:25:39 +00:00
// 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`
2024-07-05 16:49:08 +02:00
);
2026-04-02 21:25:39 +00:00
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 } `
2024-07-08 23:34:16 +02:00
);
2026-04-02 21:25:39 +00:00
}
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" )
))));
}
2026-10-12 20:38:04 +02:00
// 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 ] })));
2026-04-02 21:25:39 +00:00
}
// static/src/index.tsx
document . body . prepend (
2026-10-12 20:38:04 +02:00
/* @__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 ))
2026-04-02 21:25:39 +00:00
);
2026-10-12 20:38:04 +02:00
document . querySelector ( "main" ). append ( InfoScreen (), MainScreen (), PrivacyScreen ());
2026-04-02 21:25:39 +00:00
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 )
))))
);
2024-07-04 16:11:02 +02:00
})();