change mailbox behaviour to use reception confirmations
This commit is contained in:
@@ -5,22 +5,22 @@ The UDN project aims to provide decentralized and localhost-able infrastructure
|
|||||||
# Contents
|
# Contents
|
||||||
|
|
||||||
- [How it works](#how-it-works)
|
- [How it works](#how-it-works)
|
||||||
- [Channels](#channels)
|
- [Channels](#channels)
|
||||||
- [Scalability](#scalability)
|
- [Scalability](#scalability)
|
||||||
- [Example](#example)
|
- [Example](#example)
|
||||||
- [User's guide](#users-guide)
|
- [User's guide](#users-guide)
|
||||||
- [Getting Connected](#getting-connected)
|
- [Getting Connected](#getting-connected)
|
||||||
- [Using Apps](#using-apps)
|
- [Using Apps](#using-apps)
|
||||||
- [Getting Apps](#getting-apps)
|
- [Getting Apps](#getting-apps)
|
||||||
- [Administrator's Guide](#administrators-guide)
|
- [Administrator's Guide](#administrators-guide)
|
||||||
- [Getting Started](#getting-started)
|
- [Getting Started](#getting-started)
|
||||||
- [Configuration](#configuration)
|
- [Configuration](#configuration)
|
||||||
- [TLS](#tls)
|
- [TLS](#tls)
|
||||||
- [Connecting Multiple Servers](#connecting-multiple-servers)
|
- [Connecting Multiple Servers](#connecting-multiple-servers)
|
||||||
- [App Developer's Guide](#app-developers-guide)
|
- [App Developer's Guide](#app-developers-guide)
|
||||||
- [Guidelines](#guidelines)
|
- [Guidelines](#guidelines)
|
||||||
- [The Recommended Way](#the-recommended-way)
|
- [The Recommended Way](#the-recommended-way)
|
||||||
- [The Manual Way](#the-manual-way)
|
- [The Manual Way](#the-manual-way)
|
||||||
|
|
||||||
# How it works
|
# How it works
|
||||||
|
|
||||||
@@ -40,14 +40,14 @@ Subscriptions and messages are handled by a server:
|
|||||||
|
|
||||||
```md
|
```md
|
||||||
**Josh** --(subscribe to "my-channel")-> **Server**
|
**Josh** --(subscribe to "my-channel")-> **Server**
|
||||||
**Josh** <-(subscription confirmed )-- **Server**
|
**Josh** <-(subscription confirmed )-- **Server**
|
||||||
|
|
||||||
**Bob** --(subscribe to "my-channel")-> **Server**
|
**Bob** --(subscribe to "my-channel")-> **Server**
|
||||||
**Bob** <-(subscription confirmed )-- **Server**
|
**Bob** <-(subscription confirmed )-- **Server**
|
||||||
|
|
||||||
**Josh** --("hello" on "my-channel" )-> **Server**
|
**Josh** --("hello" on "my-channel" )-> **Server**
|
||||||
**Josh** <-("hello" on "my-channel" )-- **Server**
|
**Josh** <-("hello" on "my-channel" )-- **Server**
|
||||||
**Bob** <-("hello" on "my-channel" )-- **Server**
|
**Bob** <-("hello" on "my-channel" )-- **Server**
|
||||||
```
|
```
|
||||||
|
|
||||||
## Scalability
|
## Scalability
|
||||||
@@ -79,36 +79,36 @@ Then, John will send his messages on both "s1-and-s2" and "car-warranty". As alw
|
|||||||
# John and Bob subscribe:
|
# John and Bob subscribe:
|
||||||
|
|
||||||
**John** --(subscribe to "car-warranty")-> **S1**
|
**John** --(subscribe to "car-warranty")-> **S1**
|
||||||
**John** <-(subscription confirmed )-- **S1**
|
**John** <-(subscription confirmed )-- **S1**
|
||||||
|
|
||||||
**Bob** --(subscribe to "car-warranty")-> **S2**
|
**Bob** --(subscribe to "car-warranty")-> **S2**
|
||||||
**Bob** <-(subscription confirmed )-- **S2**
|
**Bob** <-(subscription confirmed )-- **S2**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# The servers subscribe:
|
# The servers subscribe:
|
||||||
|
|
||||||
**S1** --(subscribe to "s1-and-s2" )-> **S2**
|
**S1** --(subscribe to "s1-and-s2" )-> **S2**
|
||||||
**S2** --(subscribe to "s1-and-s2" )-> **S1**
|
**S2** --(subscribe to "s1-and-s2" )-> **S1**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Sending the Message:
|
# Sending the Message:
|
||||||
|
|
||||||
John sends the message to his server
|
John sends the message to his server
|
||||||
**John** --("hello" on: )-> **S1**
|
**John** --("hello" on: )-> **S1**
|
||||||
( - "s1-and-s2" )
|
( - "s1-and-s2" )
|
||||||
( - "car-warranty" )
|
( - "car-warranty" )
|
||||||
|
|
||||||
S2 subscribed to "s1-and-s2", so S1 sends this message to S2
|
S2 subscribed to "s1-and-s2", so S1 sends this message to S2
|
||||||
**S1** --("hello" on: )-> **S2**
|
**S1** --("hello" on: )-> **S2**
|
||||||
( - "s1-and-s2" )
|
( - "s1-and-s2" )
|
||||||
( - "car-warranty" )
|
( - "car-warranty" )
|
||||||
|
|
||||||
Bob subscribed to "car-warranty", so S2 sends this message to Bob
|
Bob subscribed to "car-warranty", so S2 sends this message to Bob
|
||||||
**S2** --("hello" on: )-> **Bob**
|
**S2** --("hello" on: )-> **Bob**
|
||||||
( - "s1-and-s2" )
|
( - "s1-and-s2" )
|
||||||
( - "car-warranty" )
|
( - "car-warranty" )
|
||||||
```
|
```
|
||||||
|
|
||||||
This requires John to know of "s1-and-s2" and, obviously, Bob to subscribe to "car-warranty". Jane and Alice could agree on their own primary channel and use "s1-and-s2" just like John did.
|
This requires John to know of "s1-and-s2" and, obviously, Bob to subscribe to "car-warranty". Jane and Alice could agree on their own primary channel and use "s1-and-s2" just like John did.
|
||||||
@@ -139,16 +139,16 @@ As you can see, there are many ways to connect multiple servers and communicate
|
|||||||
- `http://` or `https://` (the secure variant) are protocols to request data and receive a single response
|
- `http://` or `https://` (the secure variant) are protocols to request data and receive a single response
|
||||||
- To send messages, apps use so-called WebSockets instead. Their protocol is `ws://` or `wss://` (the secure variant)
|
- To send messages, apps use so-called WebSockets instead. Their protocol is `ws://` or `wss://` (the secure variant)
|
||||||
- You will need to **replace `http` with `ws`**:
|
- You will need to **replace `http` with `ws`**:
|
||||||
- `http://192.168.0.100:3000` -> `ws://192.168.0.100:3000`
|
- `http://192.168.0.100:3000` -> `ws://192.168.0.100:3000`
|
||||||
- `https://192.168.0.200:3000` -> `wss://192.168.0.200:3000`
|
- `https://192.168.0.200:3000` -> `wss://192.168.0.200:3000`
|
||||||
|
|
||||||
## Getting Apps
|
## Getting Apps
|
||||||
|
|
||||||
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/) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+17
-17
@@ -2,30 +2,30 @@
|
|||||||
const configPath = "config.json";
|
const configPath = "config.json";
|
||||||
|
|
||||||
export const defaultConfig = {
|
export const defaultConfig = {
|
||||||
port: 3000,
|
port: 3000,
|
||||||
connectedServers: [],
|
connectedServers: [],
|
||||||
subscribedChannels: [],
|
subscribedChannels: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
// METHODS
|
// METHODS
|
||||||
export async function createConfig() {
|
export async function createConfig() {
|
||||||
const configString = JSON.stringify(defaultConfig, null, 4);
|
const configString = JSON.stringify(defaultConfig, null, 4);
|
||||||
await Bun.write(configPath, configString);
|
await Bun.write(configPath, configString);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getConfig(): Promise<typeof defaultConfig> {
|
export async function getConfig(): Promise<typeof defaultConfig> {
|
||||||
try {
|
try {
|
||||||
const configString = await Bun.file(configPath).text();
|
const configString = await Bun.file(configPath).text();
|
||||||
const configObject = JSON.parse(configString);
|
const configObject = JSON.parse(configString);
|
||||||
|
|
||||||
// validate config file
|
// validate config file
|
||||||
Object.keys(defaultConfig).forEach((key) => {
|
Object.keys(defaultConfig).forEach((key) => {
|
||||||
if (!configObject[key]) throw null;
|
if (!configObject[key]) throw null;
|
||||||
});
|
});
|
||||||
|
|
||||||
return configObject;
|
return configObject;
|
||||||
} catch {
|
} catch {
|
||||||
await createConfig();
|
await createConfig();
|
||||||
return defaultConfig;
|
return defaultConfig;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
#!/usr/bin/env bun
|
#!/usr/bin/env bun
|
||||||
|
|
||||||
import {
|
import {
|
||||||
connectServers,
|
connectServers,
|
||||||
forgetConnection,
|
forgetConnection,
|
||||||
getWebSocketStats,
|
getWebSocketStats,
|
||||||
processMessage,
|
processMessage,
|
||||||
reconnectServers,
|
reconnectServers,
|
||||||
removeExpiredMailboxes,
|
removeExpiredMailboxes,
|
||||||
trackConnection,
|
trackConnection,
|
||||||
} from "./websocket-handler";
|
} from "./websocket-handler";
|
||||||
import {
|
import {
|
||||||
createFileResponse,
|
createFileResponse,
|
||||||
getVersionNumber,
|
getVersionNumber,
|
||||||
writeError,
|
writeError,
|
||||||
writeStatNumber,
|
writeStatNumber,
|
||||||
writeStatString,
|
writeStatString,
|
||||||
writeSuccess,
|
writeSuccess,
|
||||||
} from "./utility";
|
} from "./utility";
|
||||||
|
|
||||||
import Colors from "colors";
|
import Colors from "colors";
|
||||||
@@ -35,120 +35,121 @@ export type WS = ServerWebSocket<unknown> | WebSocket;
|
|||||||
|
|
||||||
// MAIN
|
// MAIN
|
||||||
async function main() {
|
async function main() {
|
||||||
// CONFIG
|
// CONFIG
|
||||||
const config = await getConfig();
|
const config = await getConfig();
|
||||||
const versionNumber = await getVersionNumber();
|
const versionNumber = await getVersionNumber();
|
||||||
|
|
||||||
// SERVER
|
// SERVER
|
||||||
const server = Bun.serve({
|
const server = Bun.serve({
|
||||||
port: config.port,
|
port: config.port,
|
||||||
|
|
||||||
fetch(request, server) {
|
fetch(request, server) {
|
||||||
// allow websockets
|
// allow websockets
|
||||||
server.upgrade(request);
|
server.upgrade(request);
|
||||||
|
|
||||||
// respond
|
// respond
|
||||||
const requestPath = new URL(request.url).pathname;
|
const requestPath = new URL(request.url).pathname;
|
||||||
switch (requestPath) {
|
switch (requestPath) {
|
||||||
case "/":
|
case "/":
|
||||||
return createFileResponse(DEFAULT_PAGE);
|
return createFileResponse(DEFAULT_PAGE);
|
||||||
case "/stats":
|
case "/stats":
|
||||||
return new Response(JSON.stringify(getAllStats()));
|
return new Response(JSON.stringify(getAllStats()));
|
||||||
default:
|
default:
|
||||||
return createFileResponse(requestPath);
|
return createFileResponse(requestPath);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
websocket: {
|
websocket: {
|
||||||
open(ws) {
|
open(ws) {
|
||||||
try {
|
try {
|
||||||
trackConnection(ws);
|
trackConnection(ws);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
writeError(error);
|
writeError(error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
message(ws, message) {
|
message(ws, message) {
|
||||||
try {
|
try {
|
||||||
processMessage(ws, message.toString(), config);
|
processMessage(ws, message.toString(), config);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
writeError(error);
|
writeError(error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
close(ws) {
|
close(ws) {
|
||||||
try {
|
try {
|
||||||
forgetConnection(ws);
|
forgetConnection(ws);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
writeError(error);
|
writeError(error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
tls: await (async () => {
|
tls: await (async () => {
|
||||||
const keyFile = Bun.file("./key.pem");
|
const keyFile = Bun.file("./key.pem");
|
||||||
const certFile = Bun.file("./cert.pem");
|
const certFile = Bun.file("./cert.pem");
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(await keyFile.exists()) == false ||
|
(await keyFile.exists()) == false ||
|
||||||
(await certFile.exists()) == false
|
(await certFile.exists()) == false
|
||||||
)
|
)
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
key: keyFile,
|
key: keyFile,
|
||||||
cert: certFile,
|
cert: certFile,
|
||||||
passphrase: process.env.TLS_PASSPHRASE,
|
passphrase: process.env.TLS_PASSPHRASE,
|
||||||
};
|
};
|
||||||
})(),
|
})(),
|
||||||
});
|
});
|
||||||
|
|
||||||
// OTHER SERVERS
|
// OTHER SERVERS
|
||||||
connectServers(config);
|
connectServers(config);
|
||||||
|
|
||||||
// AUDIT
|
// AUDIT
|
||||||
function getServerStats(): [string, string][] {
|
function getServerStats(): [string, string][] {
|
||||||
return [
|
return [
|
||||||
["server url", server.url.toString()],
|
["server url", server.url.toString()],
|
||||||
["server ip address", IP.address()],
|
["server ip address", IP.address()],
|
||||||
["server version", versionNumber],
|
["server version", versionNumber],
|
||||||
];
|
];
|
||||||
}
|
|
||||||
|
|
||||||
function getAllStats(): [string, string | number][] {
|
|
||||||
return [...getServerStats(), ...getWebSocketStats()];
|
|
||||||
}
|
|
||||||
|
|
||||||
// CLI
|
|
||||||
function updateCLI() {
|
|
||||||
console.clear();
|
|
||||||
|
|
||||||
console.log(Colors.bold.bgWhite("UNIVERSAL DECENTRALIZED NETWORK"));
|
|
||||||
console.log(
|
|
||||||
Colors.yellow("relevant output is available in the 'logs' file")
|
|
||||||
);
|
|
||||||
console.log();
|
|
||||||
|
|
||||||
console.log(new Date().toLocaleString());
|
|
||||||
console.log();
|
|
||||||
|
|
||||||
getServerStats().forEach((entry) => writeStatString(...entry));
|
|
||||||
console.log();
|
|
||||||
|
|
||||||
getWebSocketStats().forEach((entry) => writeStatNumber(...entry));
|
|
||||||
console.log();
|
|
||||||
}
|
|
||||||
|
|
||||||
// LOOP
|
|
||||||
setInterval(() => {
|
|
||||||
if (process.env.DEV != "1") {
|
|
||||||
updateCLI();
|
|
||||||
reconnectServers(config);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
removeExpiredMailboxes();
|
function getAllStats(): [string, string | number][] {
|
||||||
}, 2000);
|
return [...getServerStats(), ...getWebSocketStats()];
|
||||||
|
}
|
||||||
|
|
||||||
// FINISH
|
// CLI
|
||||||
writeSuccess(`###\nstarted up ${new Date().toLocaleString()}`);
|
function updateCLI() {
|
||||||
|
return;
|
||||||
|
console.clear();
|
||||||
|
|
||||||
|
console.log(Colors.bold.bgWhite("UNIVERSAL DECENTRALIZED NETWORK"));
|
||||||
|
console.log(
|
||||||
|
Colors.yellow("relevant output is available in the 'logs' file"),
|
||||||
|
);
|
||||||
|
console.log();
|
||||||
|
|
||||||
|
console.log(new Date().toLocaleString());
|
||||||
|
console.log();
|
||||||
|
|
||||||
|
getServerStats().forEach((entry) => writeStatString(...entry));
|
||||||
|
console.log();
|
||||||
|
|
||||||
|
getWebSocketStats().forEach((entry) => writeStatNumber(...entry));
|
||||||
|
console.log();
|
||||||
|
}
|
||||||
|
|
||||||
|
// LOOP
|
||||||
|
setInterval(() => {
|
||||||
|
if (process.env.DEV != "1") {
|
||||||
|
updateCLI();
|
||||||
|
reconnectServers(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeExpiredMailboxes();
|
||||||
|
}, 2000);
|
||||||
|
|
||||||
|
// FINISH
|
||||||
|
writeSuccess(`###\nstarted up ${new Date().toLocaleString()}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
main();
|
main();
|
||||||
|
|||||||
Generated
+748
-748
File diff suppressed because it is too large
Load Diff
+23
-23
@@ -1,25 +1,25 @@
|
|||||||
{
|
{
|
||||||
"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"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/marlon-erler/universal-decentralized-network.git"
|
"url": "git+https://github.com/marlon-erler/universal-decentralized-network.git"
|
||||||
},
|
},
|
||||||
"author": "marlon-erler",
|
"author": "marlon-erler",
|
||||||
"license": "SEE LICENSE IN LICENSE",
|
"license": "SEE LICENSE IN LICENSE",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/marlon-erler/universal-decentralized-network/issues"
|
"url": "https://github.com/marlon-erler/universal-decentralized-network/issues"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/marlon-erler/universal-decentralized-network#readme",
|
"homepage": "https://github.com/marlon-erler/universal-decentralized-network#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/bun": "^1.1.6",
|
"@types/bun": "^1.1.6",
|
||||||
"@types/ip": "^1.1.3",
|
"@types/ip": "^1.1.3",
|
||||||
"colors": "^1.4.0",
|
"colors": "^1.4.0",
|
||||||
"express": "^4.19.2",
|
"express": "^4.19.2",
|
||||||
"ip": "^2.0.1"
|
"ip": "^2.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+27
-27
@@ -1,34 +1,34 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>UDN Server</title>
|
<title>UDN Server</title>
|
||||||
|
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta
|
<meta
|
||||||
name="viewport"
|
name="viewport"
|
||||||
content="width=device-width, initial-scale=1.0, viewport-fit=cover, user-scalable=no"
|
content="width=device-width, initial-scale=1.0, viewport-fit=cover, user-scalable=no"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<meta
|
<meta
|
||||||
name="theme-color"
|
name="theme-color"
|
||||||
content="hsla(0, 0%, 100%, 100%)"
|
content="hsla(0, 0%, 100%, 100%)"
|
||||||
media="(prefers-color-scheme: light)"
|
media="(prefers-color-scheme: light)"
|
||||||
/>
|
/>
|
||||||
<meta
|
<meta
|
||||||
name="theme-color"
|
name="theme-color"
|
||||||
content="black"
|
content="black"
|
||||||
media="(prefers-color-scheme: dark)"
|
media="(prefers-color-scheme: dark)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<link rel="stylesheet" href="/styles.css" />
|
<link rel="stylesheet" href="/styles.css" />
|
||||||
<link rel="stylesheet" href="/index.css" />
|
<link rel="stylesheet" href="/index.css" />
|
||||||
<link rel="manifest" href="/manifest.json" />
|
<link rel="manifest" href="/manifest.json" />
|
||||||
<link rel="icon" href="/icon.png" />
|
<link rel="icon" href="/icon.png" />
|
||||||
<script src="/support.js" defer></script>
|
<script src="/support.js" defer></script>
|
||||||
|
|
||||||
<script src="/index.js" defer></script>
|
<script src="/index.js" defer></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<main></main>
|
<main></main>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Vendored
+797
-547
File diff suppressed because it is too large
Load Diff
Vendored
+8
-8
@@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"name": "UDN Server",
|
"name": "UDN Server",
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{
|
||||||
"src": "icon.png",
|
"src": "icon.png",
|
||||||
"sizes": "any"
|
"sizes": "any"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+401
-396
File diff suppressed because it is too large
Load Diff
Vendored
+22
-19
@@ -1,31 +1,34 @@
|
|||||||
// TABS
|
// TABS
|
||||||
document.querySelector("main").addEventListener("scroll", () => {
|
document.querySelector("main").addEventListener("scroll", () => {
|
||||||
const links = document.querySelectorAll(".tab-link");
|
const links = document.querySelectorAll(".tab-link");
|
||||||
links.forEach((element) => element.removeAttribute("active"));
|
links.forEach((element) => element.removeAttribute("active"));
|
||||||
|
|
||||||
const pageWidth = document.querySelector("main > article").offsetWidth;
|
const pageWidth = document.querySelector("main > article").offsetWidth;
|
||||||
const scroll = document.querySelector("main").scrollLeft;
|
const scroll = document.querySelector("main").scrollLeft;
|
||||||
const currentPage = Math.round(scroll / pageWidth);
|
const currentPage = Math.round(scroll / pageWidth);
|
||||||
|
|
||||||
if (links[currentPage]) links[currentPage].setAttribute("active", "");
|
if (links[currentPage]) links[currentPage].setAttribute("active", "");
|
||||||
});
|
});
|
||||||
|
|
||||||
// SERVICE WORKER
|
// SERVICE WORKER
|
||||||
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(
|
||||||
scope: "/",
|
"/sw.js",
|
||||||
});
|
{
|
||||||
if (registration.installing) {
|
scope: "/",
|
||||||
setTimeout(() => window.location.reload(), 500);
|
},
|
||||||
} else if (registration.active) {
|
);
|
||||||
console.log("Service worker active");
|
if (registration.installing) {
|
||||||
}
|
setTimeout(() => window.location.reload(), 500);
|
||||||
} catch (error) {
|
} else if (registration.active) {
|
||||||
console.error(`Could not install service worker: ${error}`);
|
console.log("Service worker active");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Could not install service worker: ${error}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
registerServiceWorker();
|
registerServiceWorker();
|
||||||
|
|||||||
Vendored
+17
-17
@@ -1,38 +1,38 @@
|
|||||||
const CACHE_VERSION = "v1";
|
const CACHE_VERSION = "v1";
|
||||||
|
|
||||||
async function handleRequest(event) {
|
async function handleRequest(event) {
|
||||||
const responsePromise = getResponse(event);
|
const responsePromise = getResponse(event);
|
||||||
event.respondWith(responsePromise);
|
event.respondWith(responsePromise);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getResponse(event) {
|
async function getResponse(event) {
|
||||||
try {
|
try {
|
||||||
return await fetchAndCache(event);
|
return await fetchAndCache(event);
|
||||||
} catch {
|
} catch {
|
||||||
return await getFromCache(event);
|
return await getFromCache(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchAndCache(event) {
|
async function fetchAndCache(event) {
|
||||||
const request = event.request;
|
const request = event.request;
|
||||||
|
|
||||||
const response = await fetch(request);
|
const response = await fetch(request);
|
||||||
|
|
||||||
const cache = await caches.open(CACHE_VERSION);
|
const cache = await caches.open(CACHE_VERSION);
|
||||||
await cache.put(request, response.clone());
|
await cache.put(request, response.clone());
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getFromCache(event) {
|
async function getFromCache(event) {
|
||||||
const request = event.request;
|
const request = event.request;
|
||||||
|
|
||||||
const cache = await caches.open(CACHE_VERSION);
|
const cache = await caches.open(CACHE_VERSION);
|
||||||
const response = await cache.match(request);
|
const response = await cache.match(request);
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.addEventListener("fetch", (event) => {
|
self.addEventListener("fetch", (event) => {
|
||||||
handleRequest(event);
|
handleRequest(event);
|
||||||
});
|
});
|
||||||
+8
-8
@@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bloatless-react": "^1.2.6",
|
"bloatless-react": "^1.2.6",
|
||||||
"esbuild": "latest",
|
"esbuild": "latest",
|
||||||
"udn-frontend": "^1.0.6"
|
"udn-frontend": "^1.0.6"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "esbuild src/index.tsx --bundle --outdir=dist"
|
"build": "esbuild src/index.tsx --bundle --outdir=dist"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+31
-31
@@ -7,41 +7,41 @@ import { isDisconnected } from "./model";
|
|||||||
import { translations } from "./translations";
|
import { translations } from "./translations";
|
||||||
|
|
||||||
document.body.prepend(
|
document.body.prepend(
|
||||||
<menu>
|
<menu>
|
||||||
<a class="tab-link" href="#info-screen" active>
|
<a class="tab-link" href="#info-screen" active>
|
||||||
<span class="icon">info</span>
|
<span class="icon">info</span>
|
||||||
{translations.info}
|
{translations.info}
|
||||||
</a>
|
</a>
|
||||||
<a class="tab-link" href="#main-screen">
|
<a class="tab-link" href="#main-screen">
|
||||||
<span class="icon">settings</span>
|
<span class="icon">settings</span>
|
||||||
{translations.settings}
|
{translations.settings}
|
||||||
</a>
|
</a>
|
||||||
<a class="tab-link" href="#message-screen">
|
<a class="tab-link" href="#message-screen">
|
||||||
<span class="icon">chat</span>
|
<span class="icon">chat</span>
|
||||||
{translations.messages}
|
{translations.messages}
|
||||||
</a>
|
</a>
|
||||||
</menu>
|
</menu>,
|
||||||
);
|
);
|
||||||
|
|
||||||
document
|
document
|
||||||
.querySelector("main")!
|
.querySelector("main")!
|
||||||
.append(InfoScreen(), MainScreen(), MessageScreen());
|
.append(InfoScreen(), MainScreen(), MessageScreen());
|
||||||
|
|
||||||
document.body.append(
|
document.body.append(
|
||||||
<div class="modal" toggle:open={isDisconnected}>
|
<div class="modal" toggle:open={isDisconnected}>
|
||||||
<div>
|
<div>
|
||||||
<main>
|
<main>
|
||||||
<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"
|
||||||
>
|
>
|
||||||
<span class="icon error" style="font-size: 3rem">
|
<span class="icon error" style="font-size: 3rem">
|
||||||
signal_disconnected
|
signal_disconnected
|
||||||
</span>
|
</span>
|
||||||
<h1 class="error">{translations.disconnected}</h1>
|
<h1 class="error">{translations.disconnected}</h1>
|
||||||
<p class="secondary">{translations.reconnecting}</p>
|
<p class="secondary">{translations.reconnecting}</p>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</div>,
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
|
|||||||
+18
-15
@@ -5,19 +5,22 @@ import { statHTMLString, updateStats } from "./model";
|
|||||||
import { translations } from "./translations";
|
import { translations } from "./translations";
|
||||||
|
|
||||||
export function InfoScreen() {
|
export function InfoScreen() {
|
||||||
return (
|
return (
|
||||||
<article id="info-screen">
|
<article id="info-screen">
|
||||||
<header>
|
<header>
|
||||||
{translations.serverInfo}
|
{translations.serverInfo}
|
||||||
<span>
|
<span>
|
||||||
<button on:click={updateStats}>
|
<button on:click={updateStats}>
|
||||||
<span class="icon">refresh</span>
|
<span class="icon">refresh</span>
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
</header>
|
</header>
|
||||||
<div>
|
<div>
|
||||||
<div class="flex-column gap" subscribe:innerHTML={statHTMLString}></div>
|
<div
|
||||||
</div>
|
class="flex-column gap"
|
||||||
</article>
|
subscribe:innerHTML={statHTMLString}
|
||||||
);
|
></div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+109
-103
@@ -1,122 +1,128 @@
|
|||||||
import * as React from "bloatless-react";
|
import * as React from "bloatless-react";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
deleteMailbox,
|
deleteMailbox,
|
||||||
isDisconnected,
|
isDisconnected,
|
||||||
isMailboxDisonnected,
|
isMailboxDisonnected,
|
||||||
isMessageEmpty,
|
isMessageEmpty,
|
||||||
lastReceivedMessage,
|
lastReceivedMessage,
|
||||||
newMessageBody,
|
newMessageBody,
|
||||||
newMessageChannel,
|
newMessageChannel,
|
||||||
requestMailbox,
|
requestMailbox,
|
||||||
sendMessage,
|
sendMessage,
|
||||||
subscribe,
|
subscribe,
|
||||||
subscriptionChannel,
|
subscriptionChannel,
|
||||||
unsubscribe,
|
unsubscribe,
|
||||||
} from "./model";
|
} from "./model";
|
||||||
|
|
||||||
import { translations } from "./translations";
|
import { translations } from "./translations";
|
||||||
|
|
||||||
export function MainScreen() {
|
export function MainScreen() {
|
||||||
// UI
|
// UI
|
||||||
return (
|
return (
|
||||||
<article id="main-screen">
|
<article id="main-screen">
|
||||||
<header>{translations.settings}</header>
|
<header>{translations.settings}</header>
|
||||||
<div>
|
<div>
|
||||||
<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
|
||||||
</div>
|
class="secondary"
|
||||||
</div>
|
subscribe:innerText={lastReceivedMessage}
|
||||||
|
></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<hr></hr>
|
<hr></hr>
|
||||||
|
|
||||||
<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
|
||||||
{translations.mailboxConnected}
|
class="secondary"
|
||||||
</p>
|
toggle:hidden={isMailboxDisonnected}
|
||||||
</div>
|
>
|
||||||
</div>
|
{translations.mailboxConnected}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="flex-row width-input justify-end">
|
<div class="flex-row width-input justify-end">
|
||||||
<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}
|
||||||
</button>
|
</button>
|
||||||
<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}
|
||||||
<span class="icon">inbox</span>
|
<span class="icon">inbox</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr></hr>
|
<hr></hr>
|
||||||
|
|
||||||
<label class="tile">
|
<label class="tile">
|
||||||
<div>
|
<div>
|
||||||
<span>{translations.subscribeToChannel}</span>
|
<span>{translations.subscribeToChannel}</span>
|
||||||
<input
|
<input
|
||||||
placeholder={translations.channel_placeholder}
|
placeholder={translations.channel_placeholder}
|
||||||
bind:value={subscriptionChannel}
|
bind:value={subscriptionChannel}
|
||||||
on:enter={subscribe}
|
on:enter={subscribe}
|
||||||
></input>
|
></input>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<div class="flex-row width-input justify-end">
|
<div class="flex-row width-input justify-end">
|
||||||
<button class="danger width-50" on:click={unsubscribe}>
|
<button class="danger width-50" on:click={unsubscribe}>
|
||||||
{translations.unsubscribe}
|
{translations.unsubscribe}
|
||||||
</button>
|
</button>
|
||||||
<button class="primary width-50" on:click={subscribe}>
|
<button class="primary width-50" on:click={subscribe}>
|
||||||
{translations.subscribe}
|
{translations.subscribe}
|
||||||
<span class="icon">arrow_forward</span>
|
<span class="icon">arrow_forward</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr></hr>
|
<hr></hr>
|
||||||
|
|
||||||
<label class="tile">
|
<label class="tile">
|
||||||
<div>
|
<div>
|
||||||
<span>{translations.channel}</span>
|
<span>{translations.channel}</span>
|
||||||
<input
|
<input
|
||||||
placeholder={translations.channel_placeholder}
|
placeholder={translations.channel_placeholder}
|
||||||
bind:value={newMessageChannel}
|
bind:value={newMessageChannel}
|
||||||
on:enter={sendMessage}
|
on:enter={sendMessage}
|
||||||
></input>
|
></input>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label class="tile">
|
<label class="tile">
|
||||||
<div>
|
<div>
|
||||||
<span>{translations.message}</span>
|
<span>{translations.message}</span>
|
||||||
<input
|
<input
|
||||||
placeholder={translations.message_placeholder}
|
placeholder={translations.message_placeholder}
|
||||||
bind:value={newMessageBody}
|
bind:value={newMessageBody}
|
||||||
on:enter={sendMessage}
|
on:enter={sendMessage}
|
||||||
></input>
|
></input>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<div class="flex-row width-input justify-end">
|
<div class="flex-row width-input justify-end">
|
||||||
<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}
|
||||||
<span class="icon">send</span>
|
<span class="icon">send</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,51 +1,51 @@
|
|||||||
import * as React from "bloatless-react";
|
import * as React from "bloatless-react";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Message,
|
Message,
|
||||||
isMessageEmpty,
|
isMessageEmpty,
|
||||||
messages,
|
messages,
|
||||||
newMessageBody,
|
newMessageBody,
|
||||||
sendMessage,
|
sendMessage,
|
||||||
} from "./model";
|
} from "./model";
|
||||||
|
|
||||||
import { translations } from "./translations";
|
import { translations } from "./translations";
|
||||||
|
|
||||||
const convertMessageToElement: React.ListItemConverter<Message> = (message) => {
|
const convertMessageToElement: React.ListItemConverter<Message> = (message) => {
|
||||||
return (
|
return (
|
||||||
<div class="tile" style="flex: 0">
|
<div class="tile" style="flex: 0">
|
||||||
<div>
|
<div>
|
||||||
<span class="secondary">{message.channel}</span>
|
<span class="secondary">{message.channel}</span>
|
||||||
<b>{message.body}</b>
|
<b>{message.body}</b>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export function MessageScreen() {
|
export function MessageScreen() {
|
||||||
return (
|
return (
|
||||||
<article id="message-screen">
|
<article id="message-screen">
|
||||||
<header>{translations.messagesReceived}</header>
|
<header>{translations.messagesReceived}</header>
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="flex-column gap"
|
class="flex-column gap"
|
||||||
subscribe:children={[messages, convertMessageToElement]}
|
subscribe:children={[messages, convertMessageToElement]}
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
<footer>
|
<footer>
|
||||||
<input
|
<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}
|
||||||
></input>
|
></input>
|
||||||
<button
|
<button
|
||||||
class="primary"
|
class="primary"
|
||||||
on:click={sendMessage}
|
on:click={sendMessage}
|
||||||
toggle:disabled={isMessageEmpty}
|
toggle:disabled={isMessageEmpty}
|
||||||
>
|
>
|
||||||
<span class="icon">send</span>
|
<span class="icon">send</span>
|
||||||
</button>
|
</button>
|
||||||
</footer>
|
</footer>
|
||||||
</article>
|
</article>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+55
-50
@@ -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,28 +21,30 @@ 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();
|
|
||||||
|
|
||||||
let html = "";
|
|
||||||
data.forEach((item) => {
|
|
||||||
const tile = (
|
|
||||||
<div class="tile">
|
|
||||||
<span class="secondary flex width-100">{item[0].toString()}</span>
|
|
||||||
<b>{item[1].toString()}</b>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
html += tile.outerHTML;
|
const data: [string, string][] = await response.json();
|
||||||
});
|
|
||||||
statHTMLString.value = html;
|
let html = "";
|
||||||
|
data.forEach((item) => {
|
||||||
|
const tile = (
|
||||||
|
<div class="tile">
|
||||||
|
<span class="secondary flex width-100">
|
||||||
|
{item[0].toString()}
|
||||||
|
</span>
|
||||||
|
<b>{item[1].toString()}</b>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
html += tile.outerHTML;
|
||||||
|
});
|
||||||
|
statHTMLString.value = html;
|
||||||
}
|
}
|
||||||
|
|
||||||
// channels
|
// channels
|
||||||
@@ -49,79 +54,79 @@ export const newMessageChannel = new React.State("");
|
|||||||
export const newMessageBody = new React.State("");
|
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
|
||||||
const UDN = new UDNFrontend();
|
const UDN = new UDNFrontend();
|
||||||
|
|
||||||
UDN.onconnect = () => {
|
UDN.onconnect = () => {
|
||||||
isDisconnected.value = false;
|
isDisconnected.value = false;
|
||||||
updateStats();
|
updateStats();
|
||||||
|
|
||||||
if (mailboxId.value != "") {
|
if (mailboxId.value != "") {
|
||||||
UDN.connectMailbox(mailboxId.value);
|
UDN.connectMailbox(mailboxId.value);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
UDN.onmessage = (data) => {
|
UDN.onmessage = (data) => {
|
||||||
lastReceivedMessage.value = JSON.stringify(data, null, 4);
|
lastReceivedMessage.value = JSON.stringify(data, null, 4);
|
||||||
|
|
||||||
const { messageChannel, messageBody } = data;
|
const { messageChannel, messageBody } = data;
|
||||||
if (messageChannel && messageBody) {
|
if (messageChannel && messageBody) {
|
||||||
const messageObject = new Message(messageChannel, messageBody);
|
const messageObject = new Message(messageChannel, messageBody);
|
||||||
if (messageBody) messages.add(messageObject);
|
if (messageBody) messages.add(messageObject);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
UDN.onmailboxcreate = (id) => {
|
UDN.onmailboxcreate = (id) => {
|
||||||
console.log(id);
|
console.log(id);
|
||||||
mailboxId.value = id;
|
mailboxId.value = id;
|
||||||
UDN.connectMailbox(id);
|
UDN.connectMailbox(id);
|
||||||
};
|
};
|
||||||
|
|
||||||
UDN.onmailboxconnect = () => {
|
UDN.onmailboxconnect = () => {
|
||||||
isMailboxDisonnected.value = false;
|
isMailboxDisonnected.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
UDN.onmailboxdelete = () => {
|
UDN.onmailboxdelete = () => {
|
||||||
isMailboxDisonnected.value = true;
|
isMailboxDisonnected.value = true;
|
||||||
}
|
};
|
||||||
|
|
||||||
UDN.ondisconnect = () => {
|
UDN.ondisconnect = () => {
|
||||||
isDisconnected.value = true;
|
isDisconnected.value = true;
|
||||||
isMailboxDisonnected.value = true;
|
isMailboxDisonnected.value = true;
|
||||||
setTimeout(connect, 1000);
|
setTimeout(connect, 1000);
|
||||||
};
|
};
|
||||||
|
|
||||||
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();
|
||||||
|
|
||||||
// METHODS
|
// METHODS
|
||||||
export function subscribe() {
|
export function subscribe() {
|
||||||
UDN.subscribe(subscriptionChannel.value);
|
UDN.subscribe(subscriptionChannel.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function unsubscribe() {
|
export function unsubscribe() {
|
||||||
UDN.unsubscribe(subscriptionChannel.value);
|
UDN.unsubscribe(subscriptionChannel.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sendMessage() {
|
export function sendMessage() {
|
||||||
if (isMessageEmpty.value == true) return;
|
if (isMessageEmpty.value == true) return;
|
||||||
UDN.sendMessage(newMessageChannel.value, newMessageBody.value);
|
UDN.sendMessage(newMessageChannel.value, newMessageBody.value);
|
||||||
newMessageBody.value = "";
|
newMessageBody.value = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function requestMailbox() {
|
export function requestMailbox() {
|
||||||
UDN.requestMailbox();
|
UDN.requestMailbox();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteMailbox() {
|
export function deleteMailbox() {
|
||||||
console.log("test");
|
console.log("test");
|
||||||
UDN.deleteMailbox(mailboxId.value);
|
UDN.deleteMailbox(mailboxId.value);
|
||||||
}
|
}
|
||||||
|
|||||||
+71
-71
@@ -1,79 +1,79 @@
|
|||||||
// STATIC
|
// STATIC
|
||||||
const staticTextEnglish = {
|
const staticTextEnglish = {
|
||||||
mailbox: "Mailbox",
|
mailbox: "Mailbox",
|
||||||
requestMailbox: "Request",
|
requestMailbox: "Request",
|
||||||
deleteMailbox: "Delete",
|
deleteMailbox: "Delete",
|
||||||
mailboxConnected: "Mailbox active",
|
mailboxConnected: "Mailbox active",
|
||||||
channel: "Channel",
|
channel: "Channel",
|
||||||
channel_placeholder: "my-channel",
|
channel_placeholder: "my-channel",
|
||||||
disconnected: "Server disconnected",
|
disconnected: "Server disconnected",
|
||||||
reconnecting: "Reconnecting...",
|
reconnecting: "Reconnecting...",
|
||||||
info: "Info",
|
info: "Info",
|
||||||
message: "Message",
|
message: "Message",
|
||||||
messages: "Messages",
|
messages: "Messages",
|
||||||
message_placeholder: "Hello, world!",
|
message_placeholder: "Hello, world!",
|
||||||
message_placeholder_generic: "Type a message...",
|
message_placeholder_generic: "Type a message...",
|
||||||
noMessagesReceived: "No messages received",
|
noMessagesReceived: "No messages received",
|
||||||
messageLastReceived: "Last received message",
|
messageLastReceived: "Last received message",
|
||||||
messagesReceived: "Received Messages",
|
messagesReceived: "Received Messages",
|
||||||
send: "Send",
|
send: "Send",
|
||||||
serverInfo: "Server Info",
|
serverInfo: "Server Info",
|
||||||
settings: "Settings",
|
settings: "Settings",
|
||||||
subscribeToChannel: "Subscribe to channel",
|
subscribeToChannel: "Subscribe to channel",
|
||||||
subscribe: "Subscribe",
|
subscribe: "Subscribe",
|
||||||
unsubscribe: "Unubscribe",
|
unsubscribe: "Unubscribe",
|
||||||
};
|
};
|
||||||
|
|
||||||
const allTranslations: { [key: string]: typeof staticTextEnglish } = {
|
const allTranslations: { [key: string]: typeof staticTextEnglish } = {
|
||||||
en: staticTextEnglish,
|
en: staticTextEnglish,
|
||||||
es: {
|
es: {
|
||||||
mailbox: "Buzón",
|
mailbox: "Buzón",
|
||||||
requestMailbox: "Solicitar",
|
requestMailbox: "Solicitar",
|
||||||
deleteMailbox: "Eliminar",
|
deleteMailbox: "Eliminar",
|
||||||
mailboxConnected: "Buzón activado",
|
mailboxConnected: "Buzón activado",
|
||||||
channel: "Canal",
|
channel: "Canal",
|
||||||
channel_placeholder: "mi-canal",
|
channel_placeholder: "mi-canal",
|
||||||
disconnected: "Sin coneción",
|
disconnected: "Sin coneción",
|
||||||
reconnecting: "Conectando...",
|
reconnecting: "Conectando...",
|
||||||
info: "Info",
|
info: "Info",
|
||||||
message: "Mensaje",
|
message: "Mensaje",
|
||||||
messages: "Mensajes",
|
messages: "Mensajes",
|
||||||
message_placeholder: "Hola!",
|
message_placeholder: "Hola!",
|
||||||
message_placeholder_generic: "Nuevo mensaje",
|
message_placeholder_generic: "Nuevo mensaje",
|
||||||
noMessagesReceived: "Sin mensajes",
|
noMessagesReceived: "Sin mensajes",
|
||||||
messageLastReceived: "Último mensaje",
|
messageLastReceived: "Último mensaje",
|
||||||
messagesReceived: "Todos los Mensajes",
|
messagesReceived: "Todos los Mensajes",
|
||||||
send: "Enviar",
|
send: "Enviar",
|
||||||
serverInfo: "Información",
|
serverInfo: "Información",
|
||||||
settings: "Configuración",
|
settings: "Configuración",
|
||||||
subscribeToChannel: "Suscribirse a un canal",
|
subscribeToChannel: "Suscribirse a un canal",
|
||||||
subscribe: "Suscribirse",
|
subscribe: "Suscribirse",
|
||||||
unsubscribe: "Cancelar",
|
unsubscribe: "Cancelar",
|
||||||
},
|
},
|
||||||
de: {
|
de: {
|
||||||
mailbox: "Briefkasten",
|
mailbox: "Briefkasten",
|
||||||
requestMailbox: "Beantragen",
|
requestMailbox: "Beantragen",
|
||||||
deleteMailbox: "Löschen",
|
deleteMailbox: "Löschen",
|
||||||
mailboxConnected: "Briefkasten aktiv",
|
mailboxConnected: "Briefkasten aktiv",
|
||||||
channel: "Kanal",
|
channel: "Kanal",
|
||||||
channel_placeholder: "mein-kanal",
|
channel_placeholder: "mein-kanal",
|
||||||
disconnected: "Verbindung getrennt",
|
disconnected: "Verbindung getrennt",
|
||||||
reconnecting: "Verbindung wird hergestellt...",
|
reconnecting: "Verbindung wird hergestellt...",
|
||||||
info: "Daten",
|
info: "Daten",
|
||||||
message: "Nachricht",
|
message: "Nachricht",
|
||||||
messages: "Nachrichten",
|
messages: "Nachrichten",
|
||||||
message_placeholder: "Hallo!",
|
message_placeholder: "Hallo!",
|
||||||
message_placeholder_generic: "Neue Nachricht",
|
message_placeholder_generic: "Neue Nachricht",
|
||||||
noMessagesReceived: "Keine Nachrichten",
|
noMessagesReceived: "Keine Nachrichten",
|
||||||
messageLastReceived: "Letzte Nachricht",
|
messageLastReceived: "Letzte Nachricht",
|
||||||
messagesReceived: "Empfangene Nachrichten",
|
messagesReceived: "Empfangene Nachrichten",
|
||||||
send: "Senden",
|
send: "Senden",
|
||||||
serverInfo: "Serverdaten",
|
serverInfo: "Serverdaten",
|
||||||
settings: "Einstellungen",
|
settings: "Einstellungen",
|
||||||
subscribeToChannel: "Kanal beitreten",
|
subscribeToChannel: "Kanal beitreten",
|
||||||
subscribe: "Beitreten",
|
subscribe: "Beitreten",
|
||||||
unsubscribe: "Verlassen",
|
unsubscribe: "Verlassen",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const language = navigator.language.substring(0, 2);
|
const language = navigator.language.substring(0, 2);
|
||||||
|
|||||||
+17
-11
@@ -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 {
|
||||||
|
|||||||
+25
-25
@@ -3,67 +3,67 @@ 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;
|
||||||
|
|
||||||
// MISC
|
// MISC
|
||||||
export function doIfIsString(data: any, cb: (data: string) => void): void {
|
export function doIfIsString(data: any, cb: (data: string) => void): void {
|
||||||
if (typeof data != "string" || data == "") return;
|
if (typeof data != "string" || data == "") return;
|
||||||
cb(data);
|
cb(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getVersionNumber(): Promise<string> {
|
export async function getVersionNumber(): Promise<string> {
|
||||||
try {
|
try {
|
||||||
const packageString = await Bun.file(PACKAGE_FILE).text();
|
const packageString = await Bun.file(PACKAGE_FILE).text();
|
||||||
const packageObject = JSON.parse(packageString);
|
const packageObject = JSON.parse(packageString);
|
||||||
return packageObject.version;
|
return packageObject.version;
|
||||||
} catch {
|
} catch {
|
||||||
return "unknown";
|
return "unknown";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SERVER
|
// SERVER
|
||||||
export function createFileResponse(requestPath: string): Response {
|
export function createFileResponse(requestPath: string): Response {
|
||||||
const fullPath = Path.join(STATIC_DIR, requestPath);
|
const fullPath = Path.join(STATIC_DIR, requestPath);
|
||||||
return new Response(Bun.file(fullPath));
|
return new Response(Bun.file(fullPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
// OUTPUT
|
// OUTPUT
|
||||||
async function writeToLogFile(message: string): Promise<void> {
|
async function writeToLogFile(message: string): Promise<void> {
|
||||||
await Fs.appendFile("logs", `${message}\n`);
|
await Fs.appendFile("logs", `${message}\n`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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())}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// logging
|
// logging
|
||||||
export function writeError(message: any): void {
|
export function writeError(message: any): void {
|
||||||
console.log(Colors.bold.red(message));
|
console.log(Colors.bold.red(message));
|
||||||
writeToLogFile(message);
|
writeToLogFile(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function writeSuccess(message: string): void {
|
export function writeSuccess(message: string): void {
|
||||||
console.log(Colors.bold.green(message));
|
console.log(Colors.bold.green(message));
|
||||||
writeToLogFile(message);
|
writeToLogFile(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
// MESSAGES
|
// MESSAGES
|
||||||
export function stringifyMessage(messageObject: WebSocketMessage): string {
|
export function stringifyMessage(messageObject: WebSocketMessage): string {
|
||||||
return JSON.stringify(messageObject);
|
return JSON.stringify(messageObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseMessage(messageString: string): Object {
|
export function parseMessage(messageString: string): Object {
|
||||||
return JSON.parse(messageString);
|
return JSON.parse(messageString);
|
||||||
}
|
}
|
||||||
+182
-165
@@ -1,10 +1,10 @@
|
|||||||
import { Mailbox, Subscriber, SubscriptionMap } from "./subscriptionHandler";
|
import { Mailbox, Subscriber, SubscriptionMap } from "./subscriptionHandler";
|
||||||
import {
|
import {
|
||||||
doIfIsString,
|
doIfIsString,
|
||||||
parseMessage,
|
parseMessage,
|
||||||
stringifyMessage,
|
stringifyMessage,
|
||||||
writeError,
|
writeError,
|
||||||
writeSuccess,
|
writeSuccess,
|
||||||
} from "./utility";
|
} from "./utility";
|
||||||
|
|
||||||
import Crypto from "crypto";
|
import Crypto from "crypto";
|
||||||
@@ -13,27 +13,30 @@ import { defaultConfig } from "./config-handler";
|
|||||||
|
|
||||||
// requests
|
// requests
|
||||||
export interface WebSocketMessage {
|
export interface WebSocketMessage {
|
||||||
uuid?: string;
|
uuid?: string;
|
||||||
|
|
||||||
// connecting to server
|
// connecting to server
|
||||||
requestingServerConnection?: boolean;
|
requestingServerConnection?: boolean;
|
||||||
|
|
||||||
// mailbox
|
// mailbox
|
||||||
requestingMailboxSetup?: boolean;
|
requestingMailboxSetup?: boolean;
|
||||||
requestedMailbox?: string;
|
requestedMailbox?: string;
|
||||||
deletingMailbox?: string;
|
deletingMailbox?: string;
|
||||||
assignedMailboxId?: string; // sent by server
|
assignedMailboxId?: string; // sent by server
|
||||||
connectedMailboxId?: string; // sent by server
|
connectedMailboxId?: string; // sent by server
|
||||||
deletedMailbox?: string; // sent by server
|
deletedMailbox?: string; // sent by server
|
||||||
|
|
||||||
// subscribing to channel
|
// subscribing to channel
|
||||||
subscribeChannel?: string;
|
subscribeChannel?: string;
|
||||||
unsubscribeChannel?: string;
|
unsubscribeChannel?: string;
|
||||||
subscribed?: boolean; // sent by server
|
subscribed?: boolean; // sent by server
|
||||||
|
|
||||||
//sending message
|
//sending message
|
||||||
messageChannel?: string;
|
messageChannel?: string;
|
||||||
messageBody?: string;
|
messageBody?: string;
|
||||||
|
|
||||||
|
// confirming message
|
||||||
|
confirmingMessageReceived?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TRACKING
|
// TRACKING
|
||||||
@@ -49,219 +52,233 @@ export const serversConnected = new Set<WebSocket>();
|
|||||||
export const serversDisconnected = new Set<string>();
|
export const serversDisconnected = new Set<string>();
|
||||||
|
|
||||||
export function getServerCount() {
|
export function getServerCount() {
|
||||||
return serversConnected.size + serversConnectedAsClient.size;
|
return serversConnected.size + serversConnectedAsClient.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function trackConnection(ws: WS) {
|
export function trackConnection(ws: WS) {
|
||||||
clientsConnected.add(ws);
|
clientsConnected.add(ws);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function forgetConnection(ws: WS): void {
|
export function forgetConnection(ws: WS): void {
|
||||||
clientsConnected.delete(ws);
|
clientsConnected.delete(ws);
|
||||||
serversConnectedAsClient.delete(ws);
|
serversConnectedAsClient.delete(ws);
|
||||||
subscriptionMap.deleteSubscriber(ws);
|
subscriptionMap.deleteSubscriber(ws);
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
// AUDIT
|
// AUDIT
|
||||||
export function getWebSocketStats(): [string, number][] {
|
export function getWebSocketStats(): [string, number][] {
|
||||||
return [
|
return [
|
||||||
["channels", subscriptionMap.subscribersPerChannel.size],
|
["channels", subscriptionMap.subscribersPerChannel.size],
|
||||||
["clients connected", clientsConnected.size],
|
["clients connected", clientsConnected.size],
|
||||||
["mailboxes", mailboxes.size],
|
["mailboxes", mailboxes.size],
|
||||||
["servers connected", getServerCount()],
|
["servers connected", getServerCount()],
|
||||||
["servers disconnected", serversDisconnected.size],
|
["servers disconnected", serversDisconnected.size],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
// OTHER SERVERS
|
// OTHER SERVERS
|
||||||
export function connectServers(config: typeof defaultConfig): void {
|
export function connectServers(config: typeof defaultConfig): void {
|
||||||
config.connectedServers.forEach((serverAddress) => {
|
config.connectedServers.forEach((serverAddress) => {
|
||||||
connectServer(serverAddress, config);
|
connectServer(serverAddress, config);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function reconnectServers(config: typeof defaultConfig): void {
|
export function reconnectServers(config: typeof defaultConfig): void {
|
||||||
[...serversDisconnected.values()].forEach((address) => {
|
[...serversDisconnected.values()].forEach((address) => {
|
||||||
connectServer(address, config);
|
connectServer(address, config);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function connectServer(
|
export function connectServer(
|
||||||
address: string,
|
address: string,
|
||||||
config: typeof defaultConfig
|
config: typeof defaultConfig,
|
||||||
): void {
|
): void {
|
||||||
try {
|
try {
|
||||||
// connect
|
// connect
|
||||||
const ws = new WebSocket(address);
|
const ws = new WebSocket(address);
|
||||||
serversConnected.add(ws);
|
serversConnected.add(ws);
|
||||||
|
|
||||||
// listen
|
// listen
|
||||||
ws.addEventListener("open", () => {
|
ws.addEventListener("open", () => {
|
||||||
serversDisconnected.delete(address);
|
serversDisconnected.delete(address);
|
||||||
|
|
||||||
writeSuccess(`connected to server at "${address}".`);
|
writeSuccess(`connected to server at "${address}".`);
|
||||||
|
|
||||||
subscribeChannels(ws, config);
|
subscribeChannels(ws, config);
|
||||||
requestServerConnection(ws);
|
requestServerConnection(ws);
|
||||||
});
|
});
|
||||||
|
|
||||||
ws.addEventListener("message", (message) => {
|
ws.addEventListener("message", (message) => {
|
||||||
processMessage(ws, message.data.toString(), config);
|
processMessage(ws, message.data.toString(), config);
|
||||||
});
|
});
|
||||||
|
|
||||||
ws.addEventListener("close", () => {
|
ws.addEventListener("close", () => {
|
||||||
serversConnected.delete(ws);
|
serversConnected.delete(ws);
|
||||||
serversDisconnected.add(address);
|
serversDisconnected.add(address);
|
||||||
writeError(`server at "${address}" disconnected`);
|
writeError(`server at "${address}" disconnected`);
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
writeError(`failed to connect to server at "${address}": ${error}`);
|
writeError(`failed to connect to server at "${address}": ${error}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function subscribeChannels(ws: WS, config: typeof defaultConfig): void {
|
function subscribeChannels(ws: WS, config: typeof defaultConfig): void {
|
||||||
config.subscribedChannels.forEach((channel) => {
|
config.subscribedChannels.forEach((channel) => {
|
||||||
subscribeChannel(ws, channel);
|
subscribeChannel(ws, channel);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function subscribeChannel(server: WS, channel: string): void {
|
export function subscribeChannel(server: WS, channel: string): void {
|
||||||
const message: WebSocketMessage = {
|
const message: WebSocketMessage = {
|
||||||
subscribeChannel: channel,
|
subscribeChannel: channel,
|
||||||
};
|
};
|
||||||
const messageString = stringifyMessage(message);
|
const messageString = stringifyMessage(message);
|
||||||
server.send(messageString);
|
server.send(messageString);
|
||||||
}
|
}
|
||||||
|
|
||||||
// PROCESS MESSAGE
|
// PROCESS MESSAGE
|
||||||
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
|
||||||
const messageObject: WebSocketMessage = parseMessage(messageString);
|
const messageObject: WebSocketMessage = parseMessage(messageString);
|
||||||
if (!messageObject.uuid) {
|
if (!messageObject.uuid) {
|
||||||
// assign if no id
|
// assign if no id
|
||||||
messageObject.uuid = Crypto.randomUUID();
|
messageObject.uuid = Crypto.randomUUID();
|
||||||
} else {
|
} else if (messageObject.confirmingMessageReceived == true) {
|
||||||
// ignore if already seen
|
// received confirmation
|
||||||
if (knownMessageIds.has(messageObject.uuid)) return;
|
mailboxes.forEach((mailbox) => {
|
||||||
}
|
if (mailbox.ws != ws) return;
|
||||||
// remember message
|
return mailbox.checkMessageReceivedConfirmation(
|
||||||
knownMessageIds.add(messageObject.uuid);
|
messageObject.uuid!,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// ignore if already seen
|
||||||
|
if (knownMessageIds.has(messageObject.uuid)) return;
|
||||||
|
}
|
||||||
|
// remember message
|
||||||
|
knownMessageIds.add(messageObject.uuid);
|
||||||
|
|
||||||
// SUBSCRIPTIONS
|
// SUBSCRIPTIONS
|
||||||
doIfIsString(messageObject.subscribeChannel, (subscribeChannel) => {
|
doIfIsString(messageObject.subscribeChannel, (subscribeChannel) => {
|
||||||
trackSubscription(ws, subscribeChannel);
|
trackSubscription(ws, subscribeChannel);
|
||||||
});
|
});
|
||||||
doIfIsString(messageObject.unsubscribeChannel, (unsubscribeChannel) => {
|
doIfIsString(messageObject.unsubscribeChannel, (unsubscribeChannel) => {
|
||||||
forgetSubscription(ws, unsubscribeChannel);
|
forgetSubscription(ws, unsubscribeChannel);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 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
|
||||||
if (messageObject.requestingMailboxSetup == true) {
|
if (messageObject.requestingMailboxSetup == true) {
|
||||||
const mailbox = new Mailbox(ws);
|
const mailbox = new Mailbox(ws);
|
||||||
mailboxes.set(mailbox.id, mailbox);
|
mailboxes.set(mailbox.id, mailbox);
|
||||||
}
|
}
|
||||||
doIfIsString(messageObject.requestedMailbox, (requestedMailbox) => {
|
doIfIsString(messageObject.requestedMailbox, (requestedMailbox) => {
|
||||||
const mailbox = mailboxes.get(requestedMailbox);
|
const mailbox = mailboxes.get(requestedMailbox);
|
||||||
if (!mailbox)
|
if (!mailbox)
|
||||||
return sendWebSocketMessage(
|
return sendWebSocketMessage(
|
||||||
{ deletedMailbox: requestedMailbox},
|
{ deletedMailbox: requestedMailbox },
|
||||||
ws
|
ws,
|
||||||
);
|
);
|
||||||
mailbox.ws = ws;
|
mailbox.ws = ws;
|
||||||
});
|
});
|
||||||
doIfIsString(messageObject.deletingMailbox, (deletingMailbox) => {
|
doIfIsString(messageObject.deletingMailbox, (deletingMailbox) => {
|
||||||
const mailbox = mailboxes.get(deletingMailbox);
|
const mailbox = mailboxes.get(deletingMailbox);
|
||||||
if (!mailbox) return;
|
if (!mailbox) return;
|
||||||
removeMailbox(mailbox);
|
removeMailbox(mailbox);
|
||||||
});
|
});
|
||||||
|
|
||||||
// OTHER SERVER
|
// OTHER SERVER
|
||||||
if (messageObject.requestingServerConnection == true) {
|
if (messageObject.requestingServerConnection == true) {
|
||||||
clientsConnected.delete(ws);
|
clientsConnected.delete(ws);
|
||||||
serversConnectedAsClient.add(ws);
|
serversConnectedAsClient.add(ws);
|
||||||
subscribeChannels(ws, config);
|
subscribeChannels(ws, config);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MAILBOX
|
// MAILBOX
|
||||||
export function removeExpiredMailboxes() {
|
export function removeExpiredMailboxes() {
|
||||||
mailboxes.forEach((mailbox) => {
|
mailboxes.forEach((mailbox) => {
|
||||||
if (mailbox.isExpired) removeMailbox(mailbox);
|
if (mailbox.isExpired) removeMailbox(mailbox);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeMailbox(mailbox: Mailbox) {
|
export function removeMailbox(mailbox: Mailbox) {
|
||||||
mailbox.delete();
|
mailbox.delete();
|
||||||
mailboxes.delete(mailbox.id);
|
mailboxes.delete(mailbox.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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, {
|
||||||
uuid,
|
uuid,
|
||||||
messageChannel,
|
messageChannel,
|
||||||
messageBody,
|
messageBody,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function requestServerConnection(ws: WS): void {
|
function requestServerConnection(ws: WS): void {
|
||||||
const messageObject = {
|
const messageObject = {
|
||||||
requestingServerConnection: true,
|
requestingServerConnection: true,
|
||||||
};
|
};
|
||||||
const messageString = stringifyMessage(messageObject);
|
const messageString = stringifyMessage(messageObject);
|
||||||
ws.send(messageString);
|
ws.send(messageString);
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
? true
|
.getChannelList(subscriber)
|
||||||
: false,
|
?.has(messageChannel)
|
||||||
};
|
? true
|
||||||
const messageString = stringifyMessage(messageObject);
|
: false,
|
||||||
subscriber.send(messageString);
|
};
|
||||||
|
const messageString = stringifyMessage(messageObject);
|
||||||
|
subscriber.send(messageString);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user