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