mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-03-17 13:21:44 +01:00
use rxjs for bakery connection
This commit is contained in:
parent
d0b11e9081
commit
789de46c56
@ -1,3 +0,0 @@
|
||||
# noStrudel server
|
||||
|
||||
This is meant to be a simple nodejs server that proxies requests to bypass CORS or access TOR or I2P
|
@ -1,52 +0,0 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
FROM node:20-alpine AS base
|
||||
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
ENV PATH="$PNPM_HOME:$PATH"
|
||||
RUN corepack enable
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY ./package*.json .
|
||||
COPY ./pnpm-lock.yaml .
|
||||
|
||||
FROM base AS prod-deps
|
||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-lockfile
|
||||
|
||||
FROM base AS build
|
||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
|
||||
|
||||
ENV VITE_COMMIT_HASH=""
|
||||
ENV VITE_APP_VERSION="Custom"
|
||||
|
||||
COPY tsconfig.json .
|
||||
COPY public ./public
|
||||
COPY src ./src
|
||||
RUN pnpm build
|
||||
|
||||
# FROM nginx:stable-alpine-slim AS main
|
||||
FROM base AS main
|
||||
|
||||
# install tor
|
||||
# copied from https://github.com/klemmchr/tor-alpine/blob/master/Dockerfile
|
||||
RUN echo '@edge https://dl-cdn.alpinelinux.org/alpine/edge/community' >> /etc/apk/repositories && \
|
||||
apk -U upgrade && \
|
||||
apk -v add tor@edge torsocks@edge
|
||||
|
||||
# remove tmp files
|
||||
RUN rm -rf /var/cache/apk/* /tmp/* /var/tmp/*
|
||||
|
||||
WORKDIR /app
|
||||
COPY --from=builder /app/dist /usr/share/nginx/html
|
||||
|
||||
# copy server
|
||||
COPY server/ /app/server/
|
||||
RUN cd /app/server/ && npm install
|
||||
|
||||
# setup entrypoint
|
||||
ADD ./docker-entrypoint.sh docker-entrypoint.sh
|
||||
RUN chmod a+x docker-entrypoint.sh
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
ENTRYPOINT ["/app/docker-entrypoint.sh"]
|
@ -1,151 +0,0 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
PROXY_PASS_BLOCK=""
|
||||
|
||||
# start tor if set to true
|
||||
if [ "$TOR_PROXY" = "true" ]; then
|
||||
echo "Starting tor socks proxy"
|
||||
|
||||
tor &
|
||||
tor_process=$!
|
||||
TOR_PROXY="127.0.0.1:9050"
|
||||
fi
|
||||
|
||||
# inject request proxy
|
||||
if [ -n "$REQUEST_PROXY" ]; then
|
||||
REQUEST_PROXY_URL="$REQUEST_PROXY"
|
||||
|
||||
if [ "$REQUEST_PROXY" = "true" ]; then
|
||||
REQUEST_PROXY_URL="127.0.0.1:8080"
|
||||
fi
|
||||
|
||||
echo "Request proxy set to $REQUEST_PROXY"
|
||||
sed -i 's/REQUEST_PROXY = ""/REQUEST_PROXY = "\/request-proxy"/g' /usr/share/nginx/html/index.html
|
||||
PROXY_PASS_BLOCK="$PROXY_PASS_BLOCK
|
||||
location /request-proxy/ {
|
||||
proxy_pass http://$REQUEST_PROXY_URL;
|
||||
rewrite ^/request-proxy/(.*) /\$1 break;
|
||||
}
|
||||
"
|
||||
|
||||
if [ -n "$PROXY_FIRST" ]; then
|
||||
echo "Telling app to use request proxy first"
|
||||
sed -i 's/PROXY_FIRST = false/PROXY_FIRST = true/g' /usr/share/nginx/html/index.html
|
||||
fi
|
||||
else
|
||||
echo "No request proxy set"
|
||||
fi
|
||||
|
||||
# inject cache relay URL
|
||||
if [ -n "$CACHE_RELAY" ]; then
|
||||
echo "Cache relay set to $CACHE_RELAY"
|
||||
sed -i 's/CACHE_RELAY_ENABLED = false/CACHE_RELAY_ENABLED = true/g' /usr/share/nginx/html/index.html
|
||||
PROXY_PASS_BLOCK="$PROXY_PASS_BLOCK
|
||||
location /local-relay {
|
||||
proxy_pass http://$CACHE_RELAY/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade \$http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
}
|
||||
"
|
||||
else
|
||||
echo "No cache relay set"
|
||||
fi
|
||||
|
||||
# inject image proxy URL
|
||||
if [ -n "$IMAGE_PROXY" ]; then
|
||||
echo "Image proxy set to $IMAGE_PROXY"
|
||||
sed -i 's/IMAGE_PROXY_PATH = ""/IMAGE_PROXY_PATH = "\/imageproxy"/g' /usr/share/nginx/html/index.html
|
||||
PROXY_PASS_BLOCK="$PROXY_PASS_BLOCK
|
||||
location /imageproxy/ {
|
||||
proxy_pass http://$IMAGE_PROXY;
|
||||
rewrite ^/imageproxy/(.*) /\$1 break;
|
||||
}
|
||||
"
|
||||
else
|
||||
echo "No Image proxy set"
|
||||
fi
|
||||
|
||||
CONF_FILE="/etc/nginx/conf.d/default.conf"
|
||||
NGINX_CONF="
|
||||
server {
|
||||
listen 80;
|
||||
|
||||
server_name localhost;
|
||||
merge_slashes off;
|
||||
|
||||
$PROXY_PASS_BLOCK
|
||||
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html index.htm;
|
||||
}
|
||||
|
||||
# Gzip settings
|
||||
gzip on;
|
||||
gzip_disable "msie6";
|
||||
gzip_vary on;
|
||||
gzip_proxied any;
|
||||
gzip_comp_level 6;
|
||||
gzip_buffers 16 8k;
|
||||
gzip_http_version 1.1;
|
||||
gzip_min_length 256;
|
||||
gzip_types
|
||||
application/atom+xml
|
||||
application/geo+json
|
||||
application/javascript
|
||||
application/x-javascript
|
||||
application/json
|
||||
application/ld+json
|
||||
application/manifest+json
|
||||
application/rdf+xml
|
||||
application/rss+xml
|
||||
application/xhtml+xml
|
||||
application/xml
|
||||
font/eot
|
||||
font/otf
|
||||
font/ttf
|
||||
image/svg+xml
|
||||
text/css
|
||||
text/javascript
|
||||
text/plain
|
||||
text/xml;
|
||||
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root /usr/share/nginx/html;
|
||||
}
|
||||
}
|
||||
"
|
||||
echo "$NGINX_CONF" > $CONF_FILE
|
||||
|
||||
_term() {
|
||||
echo "Caught SIGTERM signal!"
|
||||
|
||||
# stop node server
|
||||
if [ "$REQUEST_PROXY" = "true" ]; then
|
||||
kill -SIGTERM "$node_process" 2>/dev/null
|
||||
fi
|
||||
|
||||
# stop tor if started
|
||||
if [ "$TOR_PROXY" = "true" ]; then
|
||||
kill -SIGTERM "$tor_process" 2>/dev/null
|
||||
fi
|
||||
|
||||
# stop nginx
|
||||
kill -SIGTERM "$nginx_process" 2>/dev/null
|
||||
}
|
||||
|
||||
if [ "$REQUEST_PROXY" = "true" ]; then
|
||||
echo "Starting local request proxy"
|
||||
node server/index.js &
|
||||
node_process=$!
|
||||
fi
|
||||
|
||||
nginx -g 'daemon off;' &
|
||||
nginx_process=$!
|
||||
|
||||
trap _term SIGTERM
|
||||
|
||||
wait $nginx_process
|
@ -1,52 +0,0 @@
|
||||
var cors_proxy = require("cors-anywhere");
|
||||
var { PacProxyAgent } = require("pac-proxy-agent");
|
||||
|
||||
const { TOR_PROXY, I2P_PROXY } = process.env;
|
||||
|
||||
if (TOR_PROXY) console.log("Tor Proxy:", TOR_PROXY);
|
||||
if (I2P_PROXY) console.log("I2P Proxy:", I2P_PROXY);
|
||||
|
||||
const I2pConfig = I2P_PROXY
|
||||
? `
|
||||
if (shExpMatch(host, "*.i2p"))
|
||||
{
|
||||
return "PROXY ${I2P_PROXY}";
|
||||
}`.trim()
|
||||
: "";
|
||||
const TorConfig = TOR_PROXY
|
||||
? `
|
||||
if (shExpMatch(host, "*.onion"))
|
||||
{
|
||||
return "SOCKS5 ${TOR_PROXY}";
|
||||
}`.trim()
|
||||
: "";
|
||||
|
||||
const PACFile = `
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
function FindProxyForURL(url, host)
|
||||
{
|
||||
${I2pConfig}
|
||||
${TorConfig}
|
||||
return "DIRECT";
|
||||
}
|
||||
`.trim();
|
||||
|
||||
const PACURI = "pac+data:application/x-ns-proxy-autoconfig;base64," + btoa(PACFile);
|
||||
|
||||
var host = "127.0.0.1";
|
||||
var port = 8080;
|
||||
|
||||
cors_proxy
|
||||
.createServer({
|
||||
requireHeader: [],
|
||||
removeHeaders: ["cookie", "cookie2"],
|
||||
redirectSameOrigin: true,
|
||||
httpProxyOptions: {
|
||||
xfwd: false,
|
||||
agent: new PacProxyAgent(PACURI),
|
||||
},
|
||||
})
|
||||
.listen(port, host, () => {
|
||||
console.log("Running HTTP request proxy on " + host + ":" + port);
|
||||
});
|
@ -1,11 +0,0 @@
|
||||
{
|
||||
"name": "server",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cors-anywhere": "^0.4.4",
|
||||
"pac-proxy-agent": "^7.0.1"
|
||||
}
|
||||
}
|
335
server/pnpm-lock.yaml
generated
335
server/pnpm-lock.yaml
generated
@ -1,335 +0,0 @@
|
||||
lockfileVersion: "9.0"
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
importers:
|
||||
.:
|
||||
dependencies:
|
||||
cors-anywhere:
|
||||
specifier: ^0.4.4
|
||||
version: 0.4.4
|
||||
pac-proxy-agent:
|
||||
specifier: ^7.0.1
|
||||
version: 7.0.2
|
||||
|
||||
packages:
|
||||
"@tootallnate/quickjs-emscripten@0.23.0":
|
||||
resolution:
|
||||
{ integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA== }
|
||||
|
||||
agent-base@7.1.1:
|
||||
resolution:
|
||||
{ integrity: sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA== }
|
||||
engines: { node: ">= 14" }
|
||||
|
||||
ast-types@0.13.4:
|
||||
resolution:
|
||||
{ integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w== }
|
||||
engines: { node: ">=4" }
|
||||
|
||||
basic-ftp@5.0.5:
|
||||
resolution:
|
||||
{ integrity: sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg== }
|
||||
engines: { node: ">=10.0.0" }
|
||||
|
||||
cors-anywhere@0.4.4:
|
||||
resolution:
|
||||
{ integrity: sha512-8OBFwnzMgR4mNrAeAyOLB2EruS2z7u02of2bOu7i9kKYlZG+niS7CTHLPgEXKWW2NAOJWRry9RRCaL9lJRjNqg== }
|
||||
engines: { node: ">=0.10.0" }
|
||||
|
||||
data-uri-to-buffer@6.0.2:
|
||||
resolution:
|
||||
{ integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw== }
|
||||
engines: { node: ">= 14" }
|
||||
|
||||
debug@4.3.7:
|
||||
resolution:
|
||||
{ integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== }
|
||||
engines: { node: ">=6.0" }
|
||||
peerDependencies:
|
||||
supports-color: "*"
|
||||
peerDependenciesMeta:
|
||||
supports-color:
|
||||
optional: true
|
||||
|
||||
degenerator@5.0.1:
|
||||
resolution:
|
||||
{ integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ== }
|
||||
engines: { node: ">= 14" }
|
||||
|
||||
escodegen@2.1.0:
|
||||
resolution:
|
||||
{ integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w== }
|
||||
engines: { node: ">=6.0" }
|
||||
hasBin: true
|
||||
|
||||
esprima@4.0.1:
|
||||
resolution:
|
||||
{ integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== }
|
||||
engines: { node: ">=4" }
|
||||
hasBin: true
|
||||
|
||||
estraverse@5.3.0:
|
||||
resolution:
|
||||
{ integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== }
|
||||
engines: { node: ">=4.0" }
|
||||
|
||||
esutils@2.0.3:
|
||||
resolution:
|
||||
{ integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== }
|
||||
engines: { node: ">=0.10.0" }
|
||||
|
||||
eventemitter3@1.2.0:
|
||||
resolution:
|
||||
{ integrity: sha512-DOFqA1MF46fmZl2xtzXR3MPCRsXqgoFqdXcrCVYM3JNnfUeHTm/fh/v/iU7gBFpwkuBmoJPAm5GuhdDfSEJMJA== }
|
||||
|
||||
fs-extra@11.2.0:
|
||||
resolution:
|
||||
{ integrity: sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw== }
|
||||
engines: { node: ">=14.14" }
|
||||
|
||||
get-uri@6.0.3:
|
||||
resolution:
|
||||
{ integrity: sha512-BzUrJBS9EcUb4cFol8r4W3v1cPsSyajLSthNkz5BxbpDcHN5tIrM10E2eNvfnvBn3DaT3DUgx0OpsBKkaOpanw== }
|
||||
engines: { node: ">= 14" }
|
||||
|
||||
graceful-fs@4.2.11:
|
||||
resolution:
|
||||
{ integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== }
|
||||
|
||||
http-proxy-agent@7.0.2:
|
||||
resolution:
|
||||
{ integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig== }
|
||||
engines: { node: ">= 14" }
|
||||
|
||||
http-proxy@1.11.1:
|
||||
resolution:
|
||||
{ integrity: sha512-qz7jZarkVG3G6GMq+4VRJPSN4NkIjL4VMTNhKGd8jc25BumeJjWWvnY3A7OkCGa8W1TTxbaK3dcE0ijFalITVA== }
|
||||
engines: { node: ">=0.10.0" }
|
||||
|
||||
https-proxy-agent@7.0.5:
|
||||
resolution:
|
||||
{ integrity: sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw== }
|
||||
engines: { node: ">= 14" }
|
||||
|
||||
ip-address@9.0.5:
|
||||
resolution:
|
||||
{ integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g== }
|
||||
engines: { node: ">= 12" }
|
||||
|
||||
jsbn@1.1.0:
|
||||
resolution:
|
||||
{ integrity: sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A== }
|
||||
|
||||
jsonfile@6.1.0:
|
||||
resolution:
|
||||
{ integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== }
|
||||
|
||||
ms@2.1.3:
|
||||
resolution:
|
||||
{ integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== }
|
||||
|
||||
netmask@2.0.2:
|
||||
resolution:
|
||||
{ integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg== }
|
||||
engines: { node: ">= 0.4.0" }
|
||||
|
||||
pac-proxy-agent@7.0.2:
|
||||
resolution:
|
||||
{ integrity: sha512-BFi3vZnO9X5Qt6NRz7ZOaPja3ic0PhlsmCRYLOpN11+mWBCR6XJDqW5RF3j8jm4WGGQZtBA+bTfxYzeKW73eHg== }
|
||||
engines: { node: ">= 14" }
|
||||
|
||||
pac-resolver@7.0.1:
|
||||
resolution:
|
||||
{ integrity: sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg== }
|
||||
engines: { node: ">= 14" }
|
||||
|
||||
proxy-from-env@0.0.1:
|
||||
resolution:
|
||||
{ integrity: sha512-B9Hnta3CATuMS0q6kt5hEezOPM+V3dgaRewkFtFoaRQYTVNsHqUvFXmndH06z3QO1ZdDnRELv5vfY6zAj/gG7A== }
|
||||
|
||||
requires-port@0.0.1:
|
||||
resolution:
|
||||
{ integrity: sha512-AzPDCliPoWDSvEVYRQmpzuPhGGEnPrQz9YiOEvn+UdB9ixBpw+4IOZWtwctmpzySLZTy7ynpn47V14H4yaowtA== }
|
||||
|
||||
smart-buffer@4.2.0:
|
||||
resolution:
|
||||
{ integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== }
|
||||
engines: { node: ">= 6.0.0", npm: ">= 3.0.0" }
|
||||
|
||||
socks-proxy-agent@8.0.4:
|
||||
resolution:
|
||||
{ integrity: sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw== }
|
||||
engines: { node: ">= 14" }
|
||||
|
||||
socks@2.8.3:
|
||||
resolution:
|
||||
{ integrity: sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw== }
|
||||
engines: { node: ">= 10.0.0", npm: ">= 3.0.0" }
|
||||
|
||||
source-map@0.6.1:
|
||||
resolution:
|
||||
{ integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== }
|
||||
engines: { node: ">=0.10.0" }
|
||||
|
||||
sprintf-js@1.1.3:
|
||||
resolution:
|
||||
{ integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA== }
|
||||
|
||||
tslib@2.7.0:
|
||||
resolution:
|
||||
{ integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA== }
|
||||
|
||||
universalify@2.0.1:
|
||||
resolution:
|
||||
{ integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== }
|
||||
engines: { node: ">= 10.0.0" }
|
||||
|
||||
snapshots:
|
||||
"@tootallnate/quickjs-emscripten@0.23.0": {}
|
||||
|
||||
agent-base@7.1.1:
|
||||
dependencies:
|
||||
debug: 4.3.7
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
ast-types@0.13.4:
|
||||
dependencies:
|
||||
tslib: 2.7.0
|
||||
|
||||
basic-ftp@5.0.5: {}
|
||||
|
||||
cors-anywhere@0.4.4:
|
||||
dependencies:
|
||||
http-proxy: 1.11.1
|
||||
proxy-from-env: 0.0.1
|
||||
|
||||
data-uri-to-buffer@6.0.2: {}
|
||||
|
||||
debug@4.3.7:
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
|
||||
degenerator@5.0.1:
|
||||
dependencies:
|
||||
ast-types: 0.13.4
|
||||
escodegen: 2.1.0
|
||||
esprima: 4.0.1
|
||||
|
||||
escodegen@2.1.0:
|
||||
dependencies:
|
||||
esprima: 4.0.1
|
||||
estraverse: 5.3.0
|
||||
esutils: 2.0.3
|
||||
optionalDependencies:
|
||||
source-map: 0.6.1
|
||||
|
||||
esprima@4.0.1: {}
|
||||
|
||||
estraverse@5.3.0: {}
|
||||
|
||||
esutils@2.0.3: {}
|
||||
|
||||
eventemitter3@1.2.0: {}
|
||||
|
||||
fs-extra@11.2.0:
|
||||
dependencies:
|
||||
graceful-fs: 4.2.11
|
||||
jsonfile: 6.1.0
|
||||
universalify: 2.0.1
|
||||
|
||||
get-uri@6.0.3:
|
||||
dependencies:
|
||||
basic-ftp: 5.0.5
|
||||
data-uri-to-buffer: 6.0.2
|
||||
debug: 4.3.7
|
||||
fs-extra: 11.2.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
graceful-fs@4.2.11: {}
|
||||
|
||||
http-proxy-agent@7.0.2:
|
||||
dependencies:
|
||||
agent-base: 7.1.1
|
||||
debug: 4.3.7
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
http-proxy@1.11.1:
|
||||
dependencies:
|
||||
eventemitter3: 1.2.0
|
||||
requires-port: 0.0.1
|
||||
|
||||
https-proxy-agent@7.0.5:
|
||||
dependencies:
|
||||
agent-base: 7.1.1
|
||||
debug: 4.3.7
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
ip-address@9.0.5:
|
||||
dependencies:
|
||||
jsbn: 1.1.0
|
||||
sprintf-js: 1.1.3
|
||||
|
||||
jsbn@1.1.0: {}
|
||||
|
||||
jsonfile@6.1.0:
|
||||
dependencies:
|
||||
universalify: 2.0.1
|
||||
optionalDependencies:
|
||||
graceful-fs: 4.2.11
|
||||
|
||||
ms@2.1.3: {}
|
||||
|
||||
netmask@2.0.2: {}
|
||||
|
||||
pac-proxy-agent@7.0.2:
|
||||
dependencies:
|
||||
"@tootallnate/quickjs-emscripten": 0.23.0
|
||||
agent-base: 7.1.1
|
||||
debug: 4.3.7
|
||||
get-uri: 6.0.3
|
||||
http-proxy-agent: 7.0.2
|
||||
https-proxy-agent: 7.0.5
|
||||
pac-resolver: 7.0.1
|
||||
socks-proxy-agent: 8.0.4
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
pac-resolver@7.0.1:
|
||||
dependencies:
|
||||
degenerator: 5.0.1
|
||||
netmask: 2.0.2
|
||||
|
||||
proxy-from-env@0.0.1: {}
|
||||
|
||||
requires-port@0.0.1: {}
|
||||
|
||||
smart-buffer@4.2.0: {}
|
||||
|
||||
socks-proxy-agent@8.0.4:
|
||||
dependencies:
|
||||
agent-base: 7.1.1
|
||||
debug: 4.3.7
|
||||
socks: 2.8.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
socks@2.8.3:
|
||||
dependencies:
|
||||
ip-address: 9.0.5
|
||||
smart-buffer: 4.2.0
|
||||
|
||||
source-map@0.6.1:
|
||||
optional: true
|
||||
|
||||
sprintf-js@1.1.3: {}
|
||||
|
||||
tslib@2.7.0: {}
|
||||
|
||||
universalify@2.0.1: {}
|
@ -1,133 +0,0 @@
|
||||
import { BehaviorSubject, Subject } from "rxjs";
|
||||
import { EventTemplate, Relay, VerifiedEvent } from "nostr-tools";
|
||||
import { ControlMessage, ControlResponse } from "@satellite-earth/core/types";
|
||||
import { createDefer, Deferred } from "applesauce-core/promise";
|
||||
|
||||
import { logger } from "../../helpers/debug";
|
||||
|
||||
export default class BakeryRelay extends Relay {
|
||||
log = logger.extend("BakeryConnection");
|
||||
|
||||
isFirstConnection$ = new BehaviorSubject(true);
|
||||
isFirstAuthentication$ = new BehaviorSubject(true);
|
||||
connected$ = new BehaviorSubject(false);
|
||||
authenticated$ = new BehaviorSubject(false);
|
||||
controlResponse$ = new Subject<ControlResponse>();
|
||||
|
||||
constructor(url: string) {
|
||||
super(url);
|
||||
|
||||
// override _connected property
|
||||
Object.defineProperty(this, "_connected", {
|
||||
get: () => this.connected$.value,
|
||||
set: (v) => {
|
||||
this.connected$.next(v);
|
||||
if (v && this.isFirstConnection$.value) this.isFirstConnection$.next(false);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
sentAuthId = "";
|
||||
authPromise: Deferred<string> | null = null;
|
||||
|
||||
onChallenge = new Subject<string>();
|
||||
|
||||
authenticate(auth: string | ((evt: EventTemplate) => Promise<VerifiedEvent>)) {
|
||||
if (!this.connected) throw new Error("Not connected");
|
||||
|
||||
if (!this.authenticated$.value && !this.authPromise) {
|
||||
this.authPromise = createDefer<string>();
|
||||
|
||||
if (this.isFirstAuthentication$.value) this.authPromise.then(() => this.isFirstAuthentication$.next(false));
|
||||
|
||||
// CONTROL auth
|
||||
if (typeof auth === "string") {
|
||||
this.sendControlMessage(["CONTROL", "AUTH", "CODE", auth]);
|
||||
return this.authPromise;
|
||||
}
|
||||
|
||||
// NIP-42 auth
|
||||
this.auth(auth)
|
||||
.then((response) => {
|
||||
this.authenticated$.next(true);
|
||||
this.authPromise?.resolve(response);
|
||||
this.authPromise = null;
|
||||
})
|
||||
.catch((err) => {
|
||||
this.authPromise?.reject(err);
|
||||
this.authPromise = null;
|
||||
});
|
||||
}
|
||||
|
||||
return this.authPromise;
|
||||
}
|
||||
|
||||
_onauth = (challenge: string) => {
|
||||
this.onChallenge.next(challenge);
|
||||
};
|
||||
|
||||
_onmessage(message: MessageEvent<string>) {
|
||||
try {
|
||||
// Parse control message(s) received from node
|
||||
const data = JSON.parse(message.data);
|
||||
|
||||
switch (data[0]) {
|
||||
case "CONTROL":
|
||||
// const payload = Array.isArray(data[1]) ? data[1] : [data[1]];
|
||||
this.handleControlResponse(data as ControlResponse);
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
// use default relay message handling
|
||||
super._onmessage(message);
|
||||
}
|
||||
|
||||
onclose = () => {
|
||||
this.authenticated$.next(false);
|
||||
// @ts-expect-error
|
||||
this.connectionPromise = undefined;
|
||||
};
|
||||
|
||||
close(): void {
|
||||
super.close();
|
||||
|
||||
this.authenticated$.next(false);
|
||||
// @ts-expect-error
|
||||
this.connectionPromise = undefined;
|
||||
}
|
||||
|
||||
// Send control message to node
|
||||
sendControlMessage(message: ControlMessage) {
|
||||
return this.send(JSON.stringify(message));
|
||||
}
|
||||
|
||||
// handle control response
|
||||
handleControlResponse(response: ControlResponse) {
|
||||
switch (response[1]) {
|
||||
case "AUTH":
|
||||
if (response[2] === "SUCCESS") {
|
||||
this.authenticated$.next(true);
|
||||
this.authPromise?.resolve("Success");
|
||||
} else if (response[2] === "INVALID") {
|
||||
this.authPromise?.reject(new Error(response[3]));
|
||||
}
|
||||
this.authPromise = null;
|
||||
break;
|
||||
default:
|
||||
this.controlResponse$.next(response);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/** @deprecated use controlApi instead */
|
||||
clearDatabase() {
|
||||
this.sendControlMessage(["CONTROL", "DATABASE", "CLEAR"]);
|
||||
}
|
||||
/** @deprecated use controlApi instead */
|
||||
exportDatabase() {
|
||||
this.sendControlMessage(["CONTROL", "DATABASE", "EXPORT"]);
|
||||
}
|
||||
}
|
104
src/classes/bakery/bakery-control.ts
Normal file
104
src/classes/bakery/bakery-control.ts
Normal file
@ -0,0 +1,104 @@
|
||||
import {
|
||||
firstValueFrom,
|
||||
map,
|
||||
MonoTypeOperatorFunction,
|
||||
Observable,
|
||||
ReplaySubject,
|
||||
scan,
|
||||
share,
|
||||
shareReplay,
|
||||
skip,
|
||||
tap,
|
||||
timer,
|
||||
} from "rxjs";
|
||||
import { PrivateNodeConfig } from "@satellite-earth/core/types";
|
||||
import hash_sum from "hash-sum";
|
||||
|
||||
import BakeryRelay from "./bakery-relay";
|
||||
import { LogEntry, NetworkStateResult } from "./types";
|
||||
import { scanToArray } from "../../helpers/observable";
|
||||
|
||||
export default class BakeryControlApi {
|
||||
queries = new Map<string, Observable<any>>();
|
||||
|
||||
config: Observable<PrivateNodeConfig>;
|
||||
network: Observable<NetworkStateResult>;
|
||||
services: Observable<string[]>;
|
||||
|
||||
constructor(public bakery: BakeryRelay) {
|
||||
this.config = this.query<PrivateNodeConfig>("config", {}).pipe(shareReplay(1));
|
||||
this.network = this.query<NetworkStateResult>("network-status", {}).pipe(shareReplay(1));
|
||||
this.services = this.query<{ id: string }>("services", {}).pipe(
|
||||
scan((arr, service) => (arr.includes(service.id) ? arr : [...arr, service.id]), [] as string[]),
|
||||
shareReplay(1),
|
||||
);
|
||||
}
|
||||
|
||||
query<T extends unknown = unknown, R extends unknown = T>(
|
||||
type: string,
|
||||
args: any,
|
||||
modify: (source: Observable<T>) => Observable<R>,
|
||||
): Observable<R>;
|
||||
query<T extends unknown = unknown>(type: string, args: any): Observable<T>;
|
||||
query<T extends unknown = unknown, R extends unknown = T>(
|
||||
type: string,
|
||||
args: any,
|
||||
modify?: (source: Observable<T>) => Observable<R>,
|
||||
): Observable<R> {
|
||||
const id = hash_sum([type, args]);
|
||||
const existing = this.queries.get(id);
|
||||
if (existing) return existing;
|
||||
|
||||
let query = this.bakery.socket$
|
||||
.multiplex(
|
||||
() => ["QRY", "OPEN", type, id, args],
|
||||
() => ["QRY", "CLOSE", id],
|
||||
(m) => m[0] === "QRY" && (m[1] === "DATA" || m[1] === "ERR") && m[2] === id,
|
||||
)
|
||||
.pipe(
|
||||
map((message) => {
|
||||
// throw error
|
||||
if (message[1] === "ERR") throw new Error(message[2]);
|
||||
// return data
|
||||
else return message[3];
|
||||
}),
|
||||
// cleanup when query is complete
|
||||
tap({
|
||||
complete: () => {
|
||||
// cleanup query
|
||||
this.queries.delete(id);
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
if (modify) query = modify(query);
|
||||
|
||||
// share the observable
|
||||
query = query.pipe(shareReplay(1));
|
||||
|
||||
this.queries.set(id, query);
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
/** gets longs for a service */
|
||||
logs(filter: { service?: string; limit?: number }) {
|
||||
return this.query<LogEntry, LogEntry[]>("logs", filter, (source) => source.pipe(scanToArray()));
|
||||
}
|
||||
|
||||
async setConfigField<T extends keyof PrivateNodeConfig>(field: T, value: PrivateNodeConfig[T]) {
|
||||
await this.bakery.socket$.next(["CONTROL", "CONFIG", "SET", field, value]);
|
||||
|
||||
// wait for the next change to config
|
||||
await firstValueFrom(this.config.pipe(skip(1)));
|
||||
}
|
||||
|
||||
async setConfigFields(config: Partial<PrivateNodeConfig>) {
|
||||
for (const [field, value] of Object.entries(config)) {
|
||||
await this.bakery.socket$.next(["CONTROL", "CONFIG", "SET", field, value]);
|
||||
}
|
||||
|
||||
// wait for the next change to config
|
||||
await firstValueFrom(this.config.pipe(skip(1)));
|
||||
}
|
||||
}
|
122
src/classes/bakery/bakery-relay.ts
Normal file
122
src/classes/bakery/bakery-relay.ts
Normal file
@ -0,0 +1,122 @@
|
||||
import {
|
||||
BehaviorSubject,
|
||||
filter,
|
||||
map,
|
||||
merge,
|
||||
NEVER,
|
||||
Observable,
|
||||
of,
|
||||
OperatorFunction,
|
||||
shareReplay,
|
||||
take,
|
||||
takeWhile,
|
||||
tap,
|
||||
timeout,
|
||||
} from "rxjs";
|
||||
import { Filter, NostrEvent } from "nostr-tools";
|
||||
import { webSocket, WebSocketSubject } from "rxjs/webSocket";
|
||||
|
||||
import { logger } from "../../helpers/debug";
|
||||
import { simpleTimeout } from "applesauce-core/observable";
|
||||
|
||||
export type RequestResponse = { type: "EOSE"; id: string } | { type: "EVENT"; id: string; event: NostrEvent };
|
||||
|
||||
/** Filter request responses and only return the events */
|
||||
export function filterEvents(): OperatorFunction<RequestResponse, NostrEvent> {
|
||||
return (source) =>
|
||||
source.pipe(
|
||||
filter((r) => r.type === "EVENT"),
|
||||
map((r) => r.event),
|
||||
);
|
||||
}
|
||||
|
||||
export default class BakeryRelay {
|
||||
log = logger.extend("Bakery");
|
||||
public socket$: WebSocketSubject<any[]>;
|
||||
|
||||
connected$ = new BehaviorSubject(false);
|
||||
challenge$: Observable<string>;
|
||||
authenticated$ = new BehaviorSubject(false);
|
||||
|
||||
constructor(public url: string) {
|
||||
this.socket$ = webSocket({
|
||||
url,
|
||||
openObserver: {
|
||||
next: () => {
|
||||
this.log("Connected");
|
||||
this.connected$.next(true);
|
||||
this.authenticated$.next(false);
|
||||
},
|
||||
},
|
||||
closeObserver: {
|
||||
next: () => {
|
||||
this.log("Disconnected");
|
||||
this.connected$.next(false);
|
||||
this.authenticated$.next(false);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// create an observable for listening for AUTH
|
||||
this.challenge$ = this.socket$.pipe(
|
||||
filter((message) => message[0] === "AUTH"),
|
||||
map((m) => m[1]),
|
||||
shareReplay(1),
|
||||
);
|
||||
}
|
||||
|
||||
req(id: string, filters: Filter[]): Observable<RequestResponse> {
|
||||
return this.socket$
|
||||
.multiplex(
|
||||
() => ["REQ", id, ...filters],
|
||||
() => ["CLOSE", id],
|
||||
(message) => (message[0] === "EVENT" || message[0] === "CLOSE" || message[0] === "EOSE") && message[1] === id,
|
||||
)
|
||||
.pipe(
|
||||
// complete when CLOSE is sent
|
||||
takeWhile((m) => m[0] !== "CLOSE"),
|
||||
// pick event out of EVENT messages
|
||||
map<any[], RequestResponse>((message) => {
|
||||
if (message[0] === "EOSE") return { type: "EOSE", id: message[1] };
|
||||
else return { type: "EVENT", id: message[1], event: message[2] };
|
||||
}),
|
||||
// if no events are seen in 10s, emit EOSE
|
||||
timeout({
|
||||
first: 10_000,
|
||||
with: () => merge(of<RequestResponse>({ type: "EOSE", id }), NEVER),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
protected listenForOk(id: string) {
|
||||
return this.socket$.pipe(
|
||||
// look for OK message for event
|
||||
filter((m) => m[0] === "OK" && m[1] === id),
|
||||
// format OK message
|
||||
map((m) => ({ ok: m[2], message: m[3] })),
|
||||
// complete on first value
|
||||
take(1),
|
||||
);
|
||||
}
|
||||
|
||||
/** send an Event message */
|
||||
event(event: NostrEvent): Observable<{ ok: boolean; message?: string }> {
|
||||
this.socket$.next(["EVENT", event]);
|
||||
return this.listenForOk(event.id).pipe(
|
||||
// Throw timeout error if OK is not seen in 10s
|
||||
simpleTimeout(10_000, "Timeout"),
|
||||
);
|
||||
}
|
||||
|
||||
/** send and AUTH message */
|
||||
auth(event: NostrEvent): Observable<{ ok: boolean; message?: string }> {
|
||||
this.socket$.next(["AUTH", event]);
|
||||
|
||||
return this.listenForOk(event.id).pipe(
|
||||
// update authenticated
|
||||
tap((result) => this.authenticated$.next(result.ok)),
|
||||
// timeout after 5s for AUTH messages
|
||||
simpleTimeout(5_000, "Timeout"),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,75 +0,0 @@
|
||||
import { BehaviorSubject, Subject } from "rxjs";
|
||||
import { ControlMessage, ControlResponse } from "@satellite-earth/core/types";
|
||||
import { PrivateNodeConfig } from "@satellite-earth/core/types";
|
||||
import { DatabaseStats } from "@satellite-earth/core/types/control-api/database.js";
|
||||
import EventEmitter from "eventemitter3";
|
||||
|
||||
import BakeryRelay from "./bakery-connection";
|
||||
|
||||
type EventMap = {
|
||||
message: [ControlResponse];
|
||||
authenticated: [boolean];
|
||||
};
|
||||
|
||||
export default class BakeryControlApi extends EventEmitter<EventMap> {
|
||||
node: BakeryRelay;
|
||||
|
||||
config = new BehaviorSubject<PrivateNodeConfig | undefined>(undefined);
|
||||
/** @deprecated this should be a report */
|
||||
databaseStats = new Subject<DatabaseStats>();
|
||||
vapidKey = new BehaviorSubject<string | undefined>(undefined);
|
||||
|
||||
constructor(node: BakeryRelay) {
|
||||
super();
|
||||
this.node = node;
|
||||
|
||||
this.node.authenticated$.subscribe((authenticated) => {
|
||||
this.emit("authenticated", authenticated);
|
||||
if (authenticated) {
|
||||
this.node.sendControlMessage(["CONTROL", "CONFIG", "SUBSCRIBE"]);
|
||||
this.node.sendControlMessage(["CONTROL", "DATABASE", "SUBSCRIBE"]);
|
||||
this.node.sendControlMessage(["CONTROL", "REMOTE-AUTH", "SUBSCRIBE"]);
|
||||
}
|
||||
});
|
||||
|
||||
this.node.controlResponse$.subscribe(this.handleControlResponse.bind(this));
|
||||
}
|
||||
|
||||
handleControlResponse(response: ControlResponse) {
|
||||
this.emit("message", response);
|
||||
|
||||
switch (response[1]) {
|
||||
case "CONFIG":
|
||||
if (response[2] === "CHANGED") this.config.next(response[3]);
|
||||
break;
|
||||
|
||||
case "DATABASE":
|
||||
if (response[2] === "STATS") this.databaseStats.next(response[3]);
|
||||
break;
|
||||
|
||||
case "NOTIFICATIONS":
|
||||
if (response[2] === "VAPID-KEY") this.vapidKey.next(response[3]);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
send(message: ControlMessage) {
|
||||
if (this.node.connected) this.node.send(JSON.stringify(message));
|
||||
}
|
||||
|
||||
async setConfigField<T extends keyof PrivateNodeConfig>(field: T, value: PrivateNodeConfig[T]) {
|
||||
if (this.config.value === undefined) throw new Error("Config not synced");
|
||||
|
||||
await this.send(["CONTROL", "CONFIG", "SET", field, value]);
|
||||
|
||||
return new Promise<PrivateNodeConfig>((res) => {
|
||||
const sub = this.config.subscribe((config) => {
|
||||
if (config) res(config);
|
||||
sub.unsubscribe();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
import { ReportResults } from "@satellite-earth/core/types";
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
|
||||
import Report from "./report";
|
||||
|
||||
export default class ConversationsReport extends Report<"CONVERSATIONS"> {
|
||||
readonly type = "CONVERSATIONS";
|
||||
|
||||
value = new BehaviorSubject<ReportResults["CONVERSATIONS"][]>([]);
|
||||
|
||||
handleResult(response: ReportResults["CONVERSATIONS"]): void {
|
||||
// remove duplicates
|
||||
const next = this.value.value?.filter((r) => r.pubkey !== response.pubkey).concat(response) ?? [response];
|
||||
const sorted = next.sort(
|
||||
(a, b) => Math.max(b.lastReceived ?? 0, b.lastSent ?? 0) - Math.max(a.lastReceived ?? 0, a.lastSent ?? 0),
|
||||
);
|
||||
|
||||
this.value.next(sorted);
|
||||
}
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
import { ReportResults } from "@satellite-earth/core/types";
|
||||
|
||||
import Report from "./report";
|
||||
import { getDMRecipient, getDMSender } from "../../../helpers/nostr/dms";
|
||||
|
||||
function sortPubkeys(a: string, b: string): [string, string] {
|
||||
if (a < b) return [a, b];
|
||||
else return [b, a];
|
||||
}
|
||||
|
||||
export type ConversationResult = {
|
||||
id: string;
|
||||
pubkeys: [string, string];
|
||||
results: ReportResults["DM_SEARCH"][];
|
||||
};
|
||||
|
||||
export default class DMSearchReport extends Report<"DM_SEARCH"> {
|
||||
readonly type = "DM_SEARCH";
|
||||
|
||||
results = new BehaviorSubject<ReportResults["DM_SEARCH"][]>([]);
|
||||
conversations = new BehaviorSubject<ConversationResult[]>([]);
|
||||
|
||||
onFire() {
|
||||
this.results.next([]);
|
||||
this.conversations.next([]);
|
||||
}
|
||||
handleResult(result: ReportResults["DM_SEARCH"]) {
|
||||
this.results.next([...this.results.value, result]);
|
||||
|
||||
// add to conversations
|
||||
const sender = getDMSender(result.event);
|
||||
const recipient = getDMRecipient(result.event);
|
||||
|
||||
const pubkeys = sortPubkeys(sender, recipient);
|
||||
const id = pubkeys.join(":");
|
||||
|
||||
if (this.conversations.value.some((c) => c.id === id)) {
|
||||
// replace the conversation object
|
||||
this.conversations.next(
|
||||
this.conversations.value.map((c) => {
|
||||
if (c.id === id) return { id, pubkeys, results: [...c.results, result] };
|
||||
return c;
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
// add new conversation
|
||||
this.conversations.next([...this.conversations.value, { id, pubkeys, results: [result] }]);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
import { ReportResults } from "@satellite-earth/core/types";
|
||||
import Report from "./report";
|
||||
|
||||
export default class EventsSummaryReport extends Report<"EVENTS_SUMMARY"> {
|
||||
readonly type = "EVENTS_SUMMARY";
|
||||
|
||||
events = new BehaviorSubject<ReportResults["EVENTS_SUMMARY"][]>([]);
|
||||
|
||||
onFire(): void {
|
||||
this.events.next([]);
|
||||
}
|
||||
|
||||
handleResult(result: ReportResults["EVENTS_SUMMARY"]): void {
|
||||
if (this.events.value) this.events.next([...this.events.value, result]);
|
||||
else this.events.next([result]);
|
||||
}
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
import { ReportArguments } from "@satellite-earth/core/types";
|
||||
import Report from "./report";
|
||||
|
||||
import OverviewReport from "./overview.js";
|
||||
import ConversationsReport from "./conversations.js";
|
||||
import LogsReport from "./logs.js";
|
||||
import ServicesReport from "./services.js";
|
||||
import DMSearchReport from "./dm-search.js";
|
||||
import ScrapperStatusReport from "./scrapper-status.js";
|
||||
import ReceiverStatusReport from "./receiver-status.js";
|
||||
import NetworkStatusReport from "./network-status.js";
|
||||
import NotificationChannelsReport from "./notification-channels.js";
|
||||
import EventsSummaryReport from "./events-summary.js";
|
||||
|
||||
export const ReportClasses: {
|
||||
[k in keyof ReportArguments]?: typeof Report<k>;
|
||||
} = {
|
||||
OVERVIEW: OverviewReport,
|
||||
CONVERSATIONS: ConversationsReport,
|
||||
LOGS: LogsReport,
|
||||
SERVICES: ServicesReport,
|
||||
DM_SEARCH: DMSearchReport,
|
||||
SCRAPPER_STATUS: ScrapperStatusReport,
|
||||
RECEIVER_STATUS: ReceiverStatusReport,
|
||||
NETWORK_STATUS: NetworkStatusReport,
|
||||
NOTIFICATION_CHANNELS: NotificationChannelsReport,
|
||||
EVENTS_SUMMARY: EventsSummaryReport,
|
||||
} as const;
|
||||
|
||||
export type ReportTypes = {
|
||||
OVERVIEW: OverviewReport;
|
||||
CONVERSATIONS: ConversationsReport;
|
||||
LOGS: LogsReport;
|
||||
SERVICES: ServicesReport;
|
||||
DM_SEARCH: DMSearchReport;
|
||||
SCRAPPER_STATUS: ScrapperStatusReport;
|
||||
RECEIVER_STATUS: ReceiverStatusReport;
|
||||
NETWORK_STATUS: NetworkStatusReport;
|
||||
NOTIFICATION_CHANNELS: NotificationChannelsReport;
|
||||
EVENTS_SUMMARY: EventsSummaryReport;
|
||||
};
|
@ -1,22 +0,0 @@
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
import { ReportResults } from "@satellite-earth/core/types";
|
||||
|
||||
import Report from "./report";
|
||||
|
||||
export default class LogsReport extends Report<"LOGS"> {
|
||||
readonly type = "LOGS";
|
||||
|
||||
ids = new Set<string>();
|
||||
entries = new BehaviorSubject<ReportResults["LOGS"][]>([]);
|
||||
|
||||
handleResult(result: ReportResults["LOGS"]) {
|
||||
if (this.ids.has(result.id)) return;
|
||||
|
||||
this.ids.add(result.id);
|
||||
this.entries.next(this.entries.value.concat(result).sort((a, b) => b.timestamp - a.timestamp));
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.entries.next([]);
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
import { ReportResults } from "@satellite-earth/core/types";
|
||||
import Report from "./report";
|
||||
|
||||
export default class NetworkStatusReport extends Report<"NETWORK_STATUS"> {
|
||||
readonly type = "NETWORK_STATUS";
|
||||
|
||||
status = new BehaviorSubject<ReportResults["NETWORK_STATUS"] | undefined>(undefined);
|
||||
|
||||
handleResult(response: ReportResults["NETWORK_STATUS"]): void {
|
||||
this.status.next(response);
|
||||
}
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
import { ReportResults } from "@satellite-earth/core/types";
|
||||
import { NotificationChannel } from "@satellite-earth/core/types/control-api/notifications.js";
|
||||
|
||||
import Report from "./report";
|
||||
|
||||
export default class NotificationChannelsReport extends Report<"NOTIFICATION_CHANNELS"> {
|
||||
readonly type = "NOTIFICATION_CHANNELS";
|
||||
|
||||
channels = new BehaviorSubject<Record<string, NotificationChannel> | undefined>(undefined);
|
||||
|
||||
refresh() {
|
||||
this.channels.next({});
|
||||
this.fire();
|
||||
}
|
||||
handleResult(channel: ReportResults["NOTIFICATION_CHANNELS"]): void {
|
||||
if (Array.isArray(channel)) {
|
||||
const id = channel[1];
|
||||
|
||||
const next = { ...this.channels.value };
|
||||
delete next[id];
|
||||
this.channels.next(next);
|
||||
} else
|
||||
this.channels.next({
|
||||
...this.channels.value,
|
||||
[channel.id]: channel,
|
||||
});
|
||||
}
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
import { ReportResults } from "@satellite-earth/core/types";
|
||||
import Report from "./report";
|
||||
|
||||
export default class OverviewReport extends Report<"OVERVIEW"> {
|
||||
type = "OVERVIEW" as const;
|
||||
|
||||
value = new BehaviorSubject<ReportResults["OVERVIEW"][]>([]);
|
||||
|
||||
handleResult(response: ReportResults["OVERVIEW"]): void {
|
||||
// remove duplicates
|
||||
const next = this.value.value?.filter((r) => r.pubkey !== response.pubkey).concat(response) ?? [response];
|
||||
const sorted = next.sort((a, b) => b.events - a.events);
|
||||
|
||||
this.value.next(sorted);
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
import { ReportResults } from "@satellite-earth/core/types";
|
||||
import Report from "./report";
|
||||
|
||||
export default class ReceiverStatusReport extends Report<"RECEIVER_STATUS"> {
|
||||
readonly type = "RECEIVER_STATUS";
|
||||
|
||||
status = new BehaviorSubject<ReportResults["RECEIVER_STATUS"] | undefined>(undefined);
|
||||
|
||||
handleResult(response: ReportResults["RECEIVER_STATUS"]): void {
|
||||
this.status.next(response);
|
||||
}
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
import { ReportArguments, ReportResults } from "@satellite-earth/core/types";
|
||||
import _throttle from "lodash.throttle";
|
||||
import { nanoid } from "nanoid";
|
||||
import { Debugger } from "debug";
|
||||
|
||||
import BakeryControlApi from "../control-api";
|
||||
import { logger } from "../../../helpers/debug";
|
||||
|
||||
export default class Report<T extends keyof ReportArguments> {
|
||||
id: string;
|
||||
args: ReportArguments[T];
|
||||
running = false;
|
||||
log: Debugger;
|
||||
|
||||
error: string | undefined;
|
||||
|
||||
control: BakeryControlApi;
|
||||
constructor(id: string = nanoid(), args: ReportArguments[T], control: BakeryControlApi) {
|
||||
this.id = id;
|
||||
this.args = args;
|
||||
this.control = control;
|
||||
this.log = logger.extend(this.type + ":" + id);
|
||||
}
|
||||
|
||||
// override
|
||||
// @ts-expect-error
|
||||
readonly type: T = "unset";
|
||||
onFire(args: ReportArguments[T]) {}
|
||||
handleResult(result: ReportResults[T]) {}
|
||||
handleError(message: string) {
|
||||
this.error = message;
|
||||
}
|
||||
|
||||
// public api
|
||||
fireThrottle = _throttle(this.fire.bind(this), 10, { leading: false });
|
||||
fire() {
|
||||
this.onFire(this.args);
|
||||
// @ts-expect-error
|
||||
this.control.send(["CONTROL", "REPORT", "SUBSCRIBE", this.id, this.type, this.args]);
|
||||
this.running = true;
|
||||
}
|
||||
setArgs(args: ReportArguments[T]) {
|
||||
this.args = args;
|
||||
}
|
||||
close() {
|
||||
this.control.send(["CONTROL", "REPORT", "CLOSE", this.id]);
|
||||
this.running = false;
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
import { ReportResults } from "@satellite-earth/core/types";
|
||||
import Report from "./report";
|
||||
|
||||
export default class ScrapperStatusReport extends Report<"SCRAPPER_STATUS"> {
|
||||
readonly type = "SCRAPPER_STATUS";
|
||||
|
||||
status = new BehaviorSubject<ReportResults["SCRAPPER_STATUS"] | undefined>(undefined);
|
||||
|
||||
handleResult(response: ReportResults["SCRAPPER_STATUS"]): void {
|
||||
this.status.next(response);
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
import { ReportResults } from "@satellite-earth/core/types";
|
||||
import Report from "./report";
|
||||
|
||||
export default class ServicesReport extends Report<"SERVICES"> {
|
||||
readonly type = "SERVICES";
|
||||
|
||||
services = new BehaviorSubject<ReportResults["SERVICES"][]>([]);
|
||||
|
||||
handleResult(result: ReportResults["SERVICES"]) {
|
||||
this.services.next(this.services.value.filter((s) => s.id !== result.id).concat(result));
|
||||
}
|
||||
}
|
27
src/classes/bakery/types.ts
Normal file
27
src/classes/bakery/types.ts
Normal file
@ -0,0 +1,27 @@
|
||||
export type NetworkOutboundState = {
|
||||
available: boolean;
|
||||
running?: boolean;
|
||||
error?: string;
|
||||
};
|
||||
export type NetworkInboundState = {
|
||||
available: boolean;
|
||||
running?: boolean;
|
||||
error?: string;
|
||||
address?: string;
|
||||
};
|
||||
export type NetworkState = {
|
||||
inbound: NetworkInboundState;
|
||||
outbound: NetworkInboundState;
|
||||
};
|
||||
export type NetworkStateResult = {
|
||||
tor: NetworkState;
|
||||
hyper: NetworkState;
|
||||
i2p: NetworkState;
|
||||
};
|
||||
|
||||
export type LogEntry = {
|
||||
id: string;
|
||||
service: string;
|
||||
timestamp: number;
|
||||
message: string;
|
||||
};
|
@ -9,10 +9,9 @@ import { bakery$ } from "../../services/bakery";
|
||||
export default function RequireBakeryAuth({ children }: PropsWithChildren) {
|
||||
const location = useLocation();
|
||||
const bakery = useObservable(bakery$);
|
||||
const isFirstAuthentication = useObservable(bakery?.isFirstAuthentication$);
|
||||
const connected = useObservable(bakery?.connected$);
|
||||
const authenticated = useObservable(bakery?.authenticated$);
|
||||
const challenge = useObservable(bakery?.onChallenge);
|
||||
const challenge = useObservable(bakery?.challenge$);
|
||||
const { requestSignature } = useSigningContext();
|
||||
const navigate = useNavigate();
|
||||
|
||||
@ -24,16 +23,16 @@ export default function RequireBakeryAuth({ children }: PropsWithChildren) {
|
||||
if (loading.current) return;
|
||||
loading.current = true;
|
||||
|
||||
bakery
|
||||
.authenticate((draft) => requestSignature(draft))
|
||||
?.catch(() => {
|
||||
navigate("/bakery/connect/auth", { state: { back: (location.state?.back ?? location) satisfies To } });
|
||||
})
|
||||
.finally(() => (loading.current = false));
|
||||
// bakery
|
||||
// .authenticate((draft) => requestSignature(draft))
|
||||
// ?.catch(() => {
|
||||
// navigate("/bakery/connect/auth", { state: { back: (location.state?.back ?? location) satisfies To } });
|
||||
// })
|
||||
// .finally(() => (loading.current = false));
|
||||
}, [connected, authenticated, challenge, bakery]);
|
||||
|
||||
// initial auth UI
|
||||
if (!authenticated && isFirstAuthentication && connected)
|
||||
if (!authenticated && connected)
|
||||
return (
|
||||
<Flex direction="column" gap="2" alignItems="center" justifyContent="center" h="full">
|
||||
<Flex gap="2" alignItems="center">
|
||||
|
@ -39,7 +39,6 @@ export default function RequireBakery({ children }: PropsWithChildren & { requir
|
||||
const location = useLocation();
|
||||
const bakery = useObservable(bakery$);
|
||||
const connected = useObservable(bakery?.connected$);
|
||||
const isFirstConnection = useObservable(bakery?.isFirstConnection$);
|
||||
|
||||
// if there is no node connection, setup a connection
|
||||
if (!bakery)
|
||||
@ -51,7 +50,7 @@ export default function RequireBakery({ children }: PropsWithChildren & { requir
|
||||
/>
|
||||
);
|
||||
|
||||
if (bakery && isFirstConnection && connected === false) return <InitialConnectionOverlay />;
|
||||
if (bakery && connected === false) return <InitialConnectionOverlay />;
|
||||
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
5
src/helpers/observable.ts
Normal file
5
src/helpers/observable.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { OperatorFunction, scan } from "rxjs";
|
||||
|
||||
export function scanToArray<T extends unknown>(): OperatorFunction<T, T[]> {
|
||||
return (source) => source.pipe(scan((arr, value) => [...arr, value], [] as T[]));
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
import { useActiveAccount, useObservable } from "applesauce-react/hooks";
|
||||
|
||||
import useReport from "../use-report";
|
||||
|
||||
export default function useConversationsReport() {
|
||||
const account = useActiveAccount();
|
||||
const pubkey = account?.pubkey;
|
||||
|
||||
// hardcode the report id to 'overview' so there is only ever one
|
||||
const report = useReport("CONVERSATIONS", pubkey ? "conversations" : undefined, pubkey ? { pubkey } : undefined);
|
||||
|
||||
return useObservable(report?.value);
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
import { useObservable } from "applesauce-react/hooks";
|
||||
|
||||
import useReport from "../use-report";
|
||||
|
||||
export default function useDMSearchReport(
|
||||
query: string,
|
||||
filter?: { conversation?: [string, string]; order?: "rank" | "created_at" },
|
||||
) {
|
||||
const enabled = query.length >= 3;
|
||||
|
||||
const report = useReport(
|
||||
"DM_SEARCH",
|
||||
enabled ? `dn-search-${query}` : undefined,
|
||||
enabled ? { query, conversation: filter?.conversation, order: filter?.order } : undefined,
|
||||
);
|
||||
|
||||
const messages = useObservable(report?.results);
|
||||
const conversations = useObservable(report?.conversations);
|
||||
|
||||
return { messages, conversations };
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
import { useObservable } from "applesauce-react/hooks";
|
||||
import { ReportArguments } from "@satellite-earth/core/types";
|
||||
|
||||
import useReport from "../use-report";
|
||||
|
||||
export default function useEventsSummaryReport(id: string, args: ReportArguments["EVENTS_SUMMARY"]) {
|
||||
const report = useReport("EVENTS_SUMMARY", id, args);
|
||||
|
||||
return useObservable(report?.events);
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
import { useObservable } from "applesauce-react/hooks";
|
||||
|
||||
import useReport from "../use-report";
|
||||
|
||||
export default function useLogsReport(service?: string) {
|
||||
const report = useReport("LOGS", `logs-${service || "all"}`, { service });
|
||||
|
||||
const logs = useObservable(report?.entries);
|
||||
return { report, logs };
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
import { useObservable } from "applesauce-react/hooks";
|
||||
|
||||
import useReport from "../use-report";
|
||||
|
||||
export default function useNetworkOverviewReport() {
|
||||
const report = useReport("NETWORK_STATUS", "network-status", {});
|
||||
|
||||
return useObservable(report?.status);
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
import { useObservable } from "applesauce-react/hooks";
|
||||
|
||||
import useReport from "../use-report";
|
||||
|
||||
export default function useNotificationChannelsReport() {
|
||||
const report = useReport("NOTIFICATION_CHANNELS", "notification-channels", {});
|
||||
const channels = useObservable(report?.channels);
|
||||
|
||||
return { channels, report };
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
import { useObservable } from "applesauce-react/hooks";
|
||||
|
||||
import useReport from "../use-report";
|
||||
|
||||
export default function useOverviewReport() {
|
||||
// hardcode the report id to 'overview' so there is only ever one
|
||||
const report = useReport("OVERVIEW", "overview", {});
|
||||
|
||||
return useObservable(report?.value);
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
import { useObservable } from "applesauce-react/hooks";
|
||||
|
||||
import useReport from "../use-report";
|
||||
|
||||
export default function useReceiverStatusReport() {
|
||||
const report = useReport("RECEIVER_STATUS", "receiver-status", {});
|
||||
|
||||
return useObservable(report?.status);
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
import { useObservable } from "applesauce-react/hooks";
|
||||
|
||||
import useReport from "../use-report";
|
||||
|
||||
export default function useScrapperOverviewReport() {
|
||||
const report = useReport("SCRAPPER_STATUS", "scrapper-status", {});
|
||||
|
||||
return useObservable(report?.status);
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
import { useObservable } from "applesauce-react/hooks";
|
||||
|
||||
import useReport from "../use-report";
|
||||
|
||||
export default function useServicesReport() {
|
||||
const report = useReport("SERVICES", `services`, {});
|
||||
|
||||
return useObservable(report?.services);
|
||||
}
|
6
src/hooks/use-bakery-control.ts
Normal file
6
src/hooks/use-bakery-control.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { useObservable } from "applesauce-react/hooks";
|
||||
import { controlApi$ } from "../services/bakery";
|
||||
|
||||
export default function useBakeryControl() {
|
||||
return useObservable(controlApi$);
|
||||
}
|
@ -13,7 +13,7 @@ export default function useReconnectAction() {
|
||||
|
||||
const connect = useCallback(async () => {
|
||||
try {
|
||||
await bakery?.connect();
|
||||
// await bakery?.connect();
|
||||
} catch (error) {
|
||||
if (error instanceof Error) setError(error);
|
||||
setCount(steps[Math.min(tries, steps.length - 1)]);
|
||||
|
@ -1,34 +0,0 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { ReportArguments } from "@satellite-earth/core/types";
|
||||
import { nanoid } from "nanoid";
|
||||
|
||||
import { useObservable } from "applesauce-react/hooks";
|
||||
import reportManager$ from "../services/reports";
|
||||
|
||||
export default function useReport<T extends keyof ReportArguments>(type: T, id?: string, args?: ReportArguments[T]) {
|
||||
const [hookId] = useState(() => nanoid());
|
||||
const argsKey = JSON.stringify(args);
|
||||
|
||||
const reportManager = useObservable(reportManager$);
|
||||
|
||||
const report = useMemo(() => {
|
||||
if (id && args) return reportManager?.getOrCreateReport(type, id, args);
|
||||
}, [type, id, argsKey, reportManager]);
|
||||
|
||||
useEffect(() => {
|
||||
if (args && report) {
|
||||
// @ts-expect-error
|
||||
report.setArgs(args);
|
||||
report.fireThrottle();
|
||||
}
|
||||
}, [argsKey, report]);
|
||||
|
||||
useEffect(() => {
|
||||
if (report) {
|
||||
reportManager?.addDependency(hookId, report);
|
||||
return () => reportManager?.removeDependency(hookId, report);
|
||||
}
|
||||
}, [report, reportManager]);
|
||||
|
||||
return report;
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
import { BehaviorSubject, filter, mergeMap } from "rxjs";
|
||||
import { BehaviorSubject, combineLatest, filter, lastValueFrom, map, of, shareReplay, switchMap } from "rxjs";
|
||||
import { nip42 } from "nostr-tools";
|
||||
|
||||
import { logger } from "../helpers/debug";
|
||||
import BakeryRelay from "../classes/bakery/bakery-connection";
|
||||
import BakeryControlApi from "../classes/bakery/control-api";
|
||||
import signingService from "./signing";
|
||||
import BakeryRelay from "../classes/bakery/bakery-relay";
|
||||
import BakeryControlApi from "../classes/bakery/bakery-control";
|
||||
import localSettings from "./local-settings";
|
||||
import accounts from "./accounts";
|
||||
|
||||
@ -18,13 +18,12 @@ export function clearBakeryURL() {
|
||||
|
||||
export const bakery$ = new BehaviorSubject<BakeryRelay | null>(null);
|
||||
|
||||
// connect to the bakery when the URL changes
|
||||
localSettings.bakeryURL.subscribe((url) => {
|
||||
if (!URL.canParse(url)) return bakery$.next(null);
|
||||
|
||||
try {
|
||||
const bakery = new BakeryRelay(localSettings.bakeryURL.value);
|
||||
|
||||
bakery$.next(bakery);
|
||||
bakery$.next(new BakeryRelay(localSettings.bakeryURL.value));
|
||||
} catch (err) {
|
||||
log("Failed to create bakery connection, clearing storage");
|
||||
localSettings.bakeryURL.clear();
|
||||
@ -34,31 +33,32 @@ localSettings.bakeryURL.subscribe((url) => {
|
||||
// automatically authenticate with bakery
|
||||
bakery$
|
||||
.pipe(
|
||||
filter((r) => r !== null),
|
||||
mergeMap((r) => r.onChallenge),
|
||||
// ignore when bakery is not created
|
||||
filter((b) => b !== null),
|
||||
// watch for auth challenge and account
|
||||
switchMap((b) => combineLatest([of(b), b.challenge$, accounts.active$])),
|
||||
)
|
||||
.subscribe(async (challenge) => {
|
||||
if (!challenge) return;
|
||||
|
||||
const bakery = bakery$.value;
|
||||
if (!bakery) return;
|
||||
|
||||
const account = accounts.active;
|
||||
.subscribe(async ([bakery, challenge, account]) => {
|
||||
if (!account) return;
|
||||
|
||||
try {
|
||||
await bakery.authenticate((draft) => signingService.requestSignature(draft, account));
|
||||
const draft = nip42.makeAuthEvent(bakery.url, challenge);
|
||||
const result = await lastValueFrom(bakery.auth(await account.signEvent(draft)));
|
||||
console.log("Authenticated to relay", result);
|
||||
} catch (err) {
|
||||
console.log("Failed to authenticate with bakery", err);
|
||||
}
|
||||
});
|
||||
|
||||
export const controlApi$ = new BehaviorSubject<BakeryControlApi | null>(null);
|
||||
// create the bakery control api
|
||||
export const controlApi$ = bakery$.pipe(
|
||||
filter((b) => !!b),
|
||||
map((bakery) => new BakeryControlApi(bakery)),
|
||||
shareReplay(1),
|
||||
);
|
||||
|
||||
// create a control api for the bakery
|
||||
bakery$.subscribe((relay) => {
|
||||
if (!relay) return controlApi$.next(null);
|
||||
else controlApi$.next(new BakeryControlApi(relay));
|
||||
controlApi$.pipe(switchMap((api) => api.config)).subscribe((config) => {
|
||||
console.log("config", config);
|
||||
});
|
||||
|
||||
if (import.meta.env.DEV) {
|
||||
@ -67,10 +67,3 @@ if (import.meta.env.DEV) {
|
||||
// @ts-expect-error
|
||||
window.controlApi$ = controlApi$;
|
||||
}
|
||||
|
||||
export function getControlApi() {
|
||||
return controlApi$.value;
|
||||
}
|
||||
export function getBakery() {
|
||||
return bakery$.value;
|
||||
}
|
||||
|
@ -1,102 +0,0 @@
|
||||
import { ReportArguments, ControlResponse } from "@satellite-earth/core/types";
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
import _throttle from "lodash.throttle";
|
||||
|
||||
import BakeryControlApi from "../classes/bakery/control-api";
|
||||
import { controlApi$ } from "./bakery";
|
||||
import Report from "../classes/bakery/reports/report";
|
||||
import SuperMap from "../classes/super-map";
|
||||
import { logger } from "../helpers/debug";
|
||||
import { ReportClasses, ReportTypes } from "../classes/bakery/reports";
|
||||
|
||||
class ReportManager {
|
||||
log = logger.extend("ReportManager");
|
||||
control: BakeryControlApi;
|
||||
|
||||
reports = new Map<string, Report<any>>();
|
||||
|
||||
constructor(control: BakeryControlApi) {
|
||||
this.control = control;
|
||||
|
||||
this.control.on("message", this.handleMessage.bind(this));
|
||||
}
|
||||
|
||||
private handleMessage(message: ControlResponse) {
|
||||
if (message[1] === "REPORT") {
|
||||
const id = message[3];
|
||||
const report = this.reports.get(id);
|
||||
switch (message[2]) {
|
||||
case "RESULT":
|
||||
if (report) report.handleResult(message[4]);
|
||||
break;
|
||||
case "ERROR":
|
||||
if (report) report.handleError(message[4]);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// public api
|
||||
getOrCreateReport<T extends keyof ReportArguments>(type: T, id: string, args: ReportArguments[T]) {
|
||||
let report = this.getReport(type, id);
|
||||
if (!report) report = this.createReport(type, id, args);
|
||||
return report;
|
||||
}
|
||||
createReport<T extends keyof ReportArguments>(type: T, id: string, args: ReportArguments[T]): ReportTypes[T] {
|
||||
const ReportClass = ReportClasses[type];
|
||||
if (!ReportClass) throw new Error(`Failed to create report ${type}`);
|
||||
const report = new ReportClass(id, args, this.control);
|
||||
this.reports.set(id, report);
|
||||
// @ts-expect-error
|
||||
return report as ReportTypes[T];
|
||||
}
|
||||
getReport<T extends keyof ReportArguments>(type: T, id: string) {
|
||||
return this.reports.get(id) as ReportTypes[T] | undefined;
|
||||
}
|
||||
removeReport<T extends keyof ReportArguments>(type: T, id: string) {
|
||||
const report = this.reports.get(id) as ReportTypes[T] | undefined;
|
||||
if (report && report.running) {
|
||||
report.close();
|
||||
this.reports.delete(id);
|
||||
}
|
||||
}
|
||||
|
||||
dependencies = new SuperMap<Report<any>, Set<string>>(() => new Set());
|
||||
addDependency(id: string, report: Report<any>) {
|
||||
const set = this.dependencies.get(report);
|
||||
set.add(id);
|
||||
report.fireThrottle();
|
||||
}
|
||||
removeDependency(id: string, report: Report<any>) {
|
||||
const set = this.dependencies.get(report);
|
||||
set.delete(id);
|
||||
this.closeUnusedReportsThrottle();
|
||||
}
|
||||
|
||||
private closeUnusedReportsThrottle = _throttle(this.closeUnusedReports.bind(this), 1000, { leading: false });
|
||||
private closeUnusedReports() {
|
||||
for (const [report, dependencies] of this.dependencies) {
|
||||
if (report.running && dependencies.size === 0) {
|
||||
this.log(`Closing ${report.type} ${report.id}`);
|
||||
report.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const reportManager$ = new BehaviorSubject<ReportManager | null>(null);
|
||||
|
||||
controlApi$.subscribe((api) => {
|
||||
if (api) reportManager$.next(new ReportManager(api));
|
||||
else reportManager$.next(null);
|
||||
});
|
||||
|
||||
if (import.meta.env.DEV) {
|
||||
// @ts-expect-error
|
||||
window.reportManager$ = reportManager$;
|
||||
}
|
||||
|
||||
export default reportManager$;
|
@ -39,8 +39,8 @@ export function BakeryAuthPage() {
|
||||
if (!bakery) return;
|
||||
|
||||
try {
|
||||
if (!bakery.connected) await bakery.connect();
|
||||
await bakery.authenticate(auth);
|
||||
// if (!bakery.connected) await bakery.connect();
|
||||
// await bakery.authenticate(auth);
|
||||
|
||||
navigate(location.state?.back || "/", { replace: true });
|
||||
} catch (error) {
|
||||
|
@ -21,9 +21,11 @@ function BakeryGeneralSettingsPage() {
|
||||
|
||||
const submit = handleSubmit(async (values) => {
|
||||
if (!controlApi) return;
|
||||
await controlApi?.send(["CONTROL", "CONFIG", "SET", "name", values.name]);
|
||||
await controlApi?.send(["CONTROL", "CONFIG", "SET", "description", values.description]);
|
||||
await controlApi?.send(["CONTROL", "CONFIG", "SET", "hyperEnabled", values.hyperEnabled]);
|
||||
await controlApi.setConfigFields({
|
||||
name: values.name,
|
||||
description: values.description,
|
||||
hyperEnabled: values.hyperEnabled,
|
||||
});
|
||||
|
||||
// wait for control api to send config back
|
||||
await firstValueFrom(controlApi?.config);
|
||||
|
@ -1,10 +1,12 @@
|
||||
import { Alert, AlertIcon } from "@chakra-ui/react";
|
||||
|
||||
import useNetworkOverviewReport from "../../../../hooks/reports/use-network-status-report";
|
||||
import PanelItemString from "../../../../components/dashboard/panel-item-string";
|
||||
import useBakeryControl from "../../../../hooks/use-bakery-control";
|
||||
import { useObservable } from "applesauce-react/hooks";
|
||||
|
||||
export default function HyperInboundStatus() {
|
||||
const status = useNetworkOverviewReport();
|
||||
const control = useBakeryControl();
|
||||
const status = useObservable(control?.network);
|
||||
|
||||
if (status === undefined) return null;
|
||||
else if (!status.hyper.inbound.available) {
|
||||
|
@ -2,13 +2,12 @@ import { ReactNode } from "react";
|
||||
import { Alert, AlertIcon, FormControl, FormHelperText, Switch } from "@chakra-ui/react";
|
||||
import { useObservable } from "applesauce-react/hooks";
|
||||
|
||||
import useNetworkOverviewReport from "../../../../hooks/reports/use-network-status-report";
|
||||
import { controlApi$ } from "../../../../services/bakery";
|
||||
import useBakeryControl from "../../../../hooks/use-bakery-control";
|
||||
|
||||
export default function HyperOutboundStatus() {
|
||||
const controlApi = useObservable(controlApi$);
|
||||
const config = useObservable(controlApi?.config);
|
||||
const status = useNetworkOverviewReport();
|
||||
const control = useBakeryControl();
|
||||
const config = useObservable(control?.config);
|
||||
const status = useObservable(control?.network);
|
||||
|
||||
let content: ReactNode = null;
|
||||
if (status === undefined) content = null;
|
||||
@ -40,9 +39,7 @@ export default function HyperOutboundStatus() {
|
||||
<FormControl>
|
||||
<Switch
|
||||
isChecked={config?.enableHyperConnections}
|
||||
onChange={(e) =>
|
||||
controlApi?.send(["CONTROL", "CONFIG", "SET", "enableHyperConnections", e.currentTarget.checked])
|
||||
}
|
||||
onChange={(e) => control?.setConfigField("enableHyperConnections", e.currentTarget.checked)}
|
||||
>
|
||||
Connect to hyper relays
|
||||
</Switch>
|
||||
|
@ -2,15 +2,14 @@ import { ReactNode } from "react";
|
||||
import { Alert, Button, Code, Flex, Heading, Link, Spinner, Switch } from "@chakra-ui/react";
|
||||
import { useObservable } from "applesauce-react/hooks";
|
||||
|
||||
import useNetworkOverviewReport from "../../../../hooks/reports/use-network-status-report";
|
||||
import HyperInboundStatus from "./hyper-inbound";
|
||||
import HyperOutboundStatus from "./hyper-outbound";
|
||||
import { controlApi$ } from "../../../../services/bakery";
|
||||
import useBakeryControl from "../../../../hooks/use-bakery-control";
|
||||
|
||||
export default function HyperNetworkStatus() {
|
||||
const controlApi = useObservable(controlApi$);
|
||||
const config = useObservable(controlApi?.config);
|
||||
const status = useNetworkOverviewReport();
|
||||
const control = useBakeryControl();
|
||||
const config = useObservable(control?.config);
|
||||
const status = useObservable(control?.network);
|
||||
|
||||
let content: ReactNode = null;
|
||||
|
||||
@ -19,11 +18,7 @@ export default function HyperNetworkStatus() {
|
||||
content = (
|
||||
<Alert status="info" whiteSpace="pre-wrap">
|
||||
Enable HyperDHT in order to connect to <Code>.hyper</Code> relays
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => controlApi?.send(["CONTROL", "CONFIG", "SET", "hyperEnabled", true])}
|
||||
ml="auto"
|
||||
>
|
||||
<Button variant="ghost" onClick={() => control?.setConfigField("hyperEnabled", true)} ml="auto">
|
||||
Enable
|
||||
</Button>
|
||||
</Alert>
|
||||
@ -43,7 +38,7 @@ export default function HyperNetworkStatus() {
|
||||
{config !== undefined && (
|
||||
<Switch
|
||||
isChecked={config?.hyperEnabled}
|
||||
onChange={(e) => controlApi?.send(["CONTROL", "CONFIG", "SET", "hyperEnabled", e.currentTarget.checked])}
|
||||
onChange={(e) => control?.setConfigField("hyperEnabled", e.currentTarget.checked)}
|
||||
>
|
||||
Enabled
|
||||
</Switch>
|
||||
|
@ -1,10 +1,12 @@
|
||||
import { Alert, AlertIcon, Spinner } from "@chakra-ui/react";
|
||||
import { useObservable } from "applesauce-react/hooks";
|
||||
|
||||
import useNetworkOverviewReport from "../../../../hooks/reports/use-network-status-report";
|
||||
import PanelItemString from "../../../../components/dashboard/panel-item-string";
|
||||
import useBakeryControl from "../../../../hooks/use-bakery-control";
|
||||
|
||||
export default function I2PInboundStatus() {
|
||||
const status = useNetworkOverviewReport();
|
||||
const control = useBakeryControl();
|
||||
const status = useObservable(control?.network);
|
||||
|
||||
if (status === undefined) return <Spinner />;
|
||||
else if (!status.i2p.inbound.available) {
|
||||
|
@ -2,13 +2,12 @@ import { ReactNode } from "react";
|
||||
import { Alert, AlertIcon, FormControl, FormHelperText, Switch } from "@chakra-ui/react";
|
||||
import { useObservable } from "applesauce-react/hooks";
|
||||
|
||||
import { controlApi$ } from "../../../../services/bakery";
|
||||
import useNetworkOverviewReport from "../../../../hooks/reports/use-network-status-report";
|
||||
import useBakeryControl from "../../../../hooks/use-bakery-control";
|
||||
|
||||
export default function I2POutboundStatus() {
|
||||
const controlApi = useObservable(controlApi$);
|
||||
const config = useObservable(controlApi?.config);
|
||||
const status = useNetworkOverviewReport();
|
||||
const control = useBakeryControl();
|
||||
const config = useObservable(control?.config);
|
||||
const status = useObservable(control?.network);
|
||||
|
||||
let content: ReactNode = null;
|
||||
if (status === undefined) content = null;
|
||||
@ -41,7 +40,7 @@ export default function I2POutboundStatus() {
|
||||
<FormControl>
|
||||
<Switch
|
||||
isChecked={config?.enableI2PConnections}
|
||||
onChange={(e) => controlApi?.setConfigField("enableI2PConnections", e.currentTarget.checked)}
|
||||
onChange={(e) => control?.setConfigField("enableI2PConnections", e.currentTarget.checked)}
|
||||
>
|
||||
Connect to i2p relays
|
||||
</Switch>
|
||||
|
@ -4,13 +4,12 @@ import { useObservable } from "applesauce-react/hooks";
|
||||
|
||||
import I2POutboundStatus from "./i2p-outbound";
|
||||
import I2PInboundStatus from "./i2p-inbound";
|
||||
import { controlApi$ } from "../../../../services/bakery";
|
||||
import useNetworkOverviewReport from "../../../../hooks/reports/use-network-status-report";
|
||||
import useBakeryControl from "../../../../hooks/use-bakery-control";
|
||||
|
||||
export default function I2PNetworkStatus() {
|
||||
const controlApi = useObservable(controlApi$);
|
||||
const config = useObservable(controlApi?.config);
|
||||
const status = useNetworkOverviewReport();
|
||||
const control = useBakeryControl();
|
||||
const config = useObservable(control?.config);
|
||||
const status = useObservable(control?.network);
|
||||
|
||||
let content: ReactNode = null;
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Box, Divider, Flex } from "@chakra-ui/react";
|
||||
import { Box, Divider } from "@chakra-ui/react";
|
||||
|
||||
import HyperNetworkStatus from "./hyper";
|
||||
import TorNetworkStatus from "./tor";
|
||||
|
@ -1,10 +1,12 @@
|
||||
import { Alert, AlertIcon, Spinner } from "@chakra-ui/react";
|
||||
|
||||
import useNetworkOverviewReport from "../../../../hooks/reports/use-network-status-report";
|
||||
import PanelItemString from "../../../../components/dashboard/panel-item-string";
|
||||
import useBakeryControl from "../../../../hooks/use-bakery-control";
|
||||
import { useObservable } from "applesauce-react/hooks";
|
||||
|
||||
export default function TorInboundStatus() {
|
||||
const status = useNetworkOverviewReport();
|
||||
const control = useBakeryControl();
|
||||
const status = useObservable(control?.network);
|
||||
|
||||
if (status === undefined) return <Spinner />;
|
||||
else if (!status.tor.inbound.available) {
|
||||
|
@ -2,13 +2,12 @@ import { ReactNode } from "react";
|
||||
import { Alert, AlertIcon, FormControl, FormHelperText, Switch } from "@chakra-ui/react";
|
||||
import { useObservable } from "applesauce-react/hooks";
|
||||
|
||||
import { controlApi$ } from "../../../../services/bakery";
|
||||
import useNetworkOverviewReport from "../../../../hooks/reports/use-network-status-report";
|
||||
import useBakeryControl from "../../../../hooks/use-bakery-control";
|
||||
|
||||
export default function TorOutboundStatus() {
|
||||
const controlApi = useObservable(controlApi$);
|
||||
const config = useObservable(controlApi?.config);
|
||||
const status = useNetworkOverviewReport();
|
||||
const control = useBakeryControl();
|
||||
const config = useObservable(control?.config);
|
||||
const status = useObservable(control?.network);
|
||||
|
||||
let content: ReactNode = null;
|
||||
if (status === undefined) content = null;
|
||||
@ -41,7 +40,7 @@ export default function TorOutboundStatus() {
|
||||
<FormControl>
|
||||
<Switch
|
||||
isChecked={config?.enableTorConnections}
|
||||
onChange={(e) => controlApi?.setConfigField("enableTorConnections", e.currentTarget.checked)}
|
||||
onChange={(e) => control?.setConfigField("enableTorConnections", e.currentTarget.checked)}
|
||||
>
|
||||
Connect to tor relays
|
||||
</Switch>
|
||||
@ -52,7 +51,7 @@ export default function TorOutboundStatus() {
|
||||
<FormControl>
|
||||
<Switch
|
||||
isChecked={config?.routeAllTrafficThroughTor}
|
||||
onChange={(e) => controlApi?.setConfigField("routeAllTrafficThroughTor", e.currentTarget.checked)}
|
||||
onChange={(e) => control?.setConfigField("routeAllTrafficThroughTor", e.currentTarget.checked)}
|
||||
>
|
||||
Route all traffic through tor proxy
|
||||
</Switch>
|
||||
|
@ -4,13 +4,12 @@ import { useObservable } from "applesauce-react/hooks";
|
||||
|
||||
import TorOutboundStatus from "./tor-outbound";
|
||||
import TorInboundStatus from "./tor-inbound";
|
||||
import { controlApi$ } from "../../../../services/bakery";
|
||||
import useNetworkOverviewReport from "../../../../hooks/reports/use-network-status-report";
|
||||
import useBakeryControl from "../../../../hooks/use-bakery-control";
|
||||
|
||||
export default function TorNetworkStatus() {
|
||||
const controlApi = useObservable(controlApi$);
|
||||
const config = useObservable(controlApi?.config);
|
||||
const status = useNetworkOverviewReport();
|
||||
const control = useBakeryControl();
|
||||
const config = useObservable(control?.config);
|
||||
const status = useObservable(control?.network);
|
||||
|
||||
let content: ReactNode = null;
|
||||
|
||||
|
@ -23,7 +23,7 @@ function EmailForm() {
|
||||
}, [config]);
|
||||
|
||||
const submit = handleSubmit((values) => {
|
||||
controlApi?.send(["CONTROL", "CONFIG", "SET", "notificationEmail", values.email]);
|
||||
controlApi?.setConfigField("notificationEmail", values.email);
|
||||
});
|
||||
|
||||
return (
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { useState } from "react";
|
||||
import { Alert, Button, Code, Flex, Heading, Link, Spinner, Text } from "@chakra-ui/react";
|
||||
import { nanoid } from "nanoid";
|
||||
import { kinds, NostrEvent } from "nostr-tools";
|
||||
import { NostrEvent } from "nostr-tools";
|
||||
import { useObservable } from "applesauce-react/hooks";
|
||||
import { NotificationChannel } from "@satellite-earth/core/types/control-api/notifications.js";
|
||||
|
||||
import { useActiveAccount } from "applesauce-react/hooks";
|
||||
import { bakery$, controlApi$ } from "../../../../services/bakery";
|
||||
import localSettings from "../../../../services/local-settings";
|
||||
import useNotificationChannelsReport from "../../../../hooks/reports/use-notification-channels";
|
||||
import { CopyIconButton } from "../../../../components/copy-icon-button";
|
||||
import { ExternalLinkIcon } from "../../../../components/icons";
|
||||
|
||||
@ -19,7 +19,8 @@ export default function NtfyNotificationSettings() {
|
||||
const device = useObservable(localSettings.deviceId);
|
||||
const topic = useObservable(localSettings.ntfyTopic);
|
||||
const server = useObservable(localSettings.ntfyServer);
|
||||
const { channels } = useNotificationChannelsReport();
|
||||
// const { channels } = useNotificationChannelsReport();
|
||||
const channels: Record<string, NotificationChannel> = {};
|
||||
|
||||
const channel = Object.values(channels || {}).find((c) => c.device === device && c.type === "ntfy");
|
||||
|
||||
@ -28,18 +29,18 @@ export default function NtfyNotificationSettings() {
|
||||
// generate a new random id
|
||||
localSettings.ntfyTopic.next(topic);
|
||||
|
||||
controlApi?.send([
|
||||
"CONTROL",
|
||||
"NOTIFICATIONS",
|
||||
"REGISTER",
|
||||
{ id: `ntfy:${topic}`, server, topic, type: "ntfy", device },
|
||||
]);
|
||||
// controlApi?.send([
|
||||
// "CONTROL",
|
||||
// "NOTIFICATIONS",
|
||||
// "REGISTER",
|
||||
// { id: `ntfy:${topic}`, server, topic, type: "ntfy", device },
|
||||
// ]);
|
||||
};
|
||||
|
||||
const disable = () => {
|
||||
if (!channel) return;
|
||||
|
||||
controlApi?.send(["CONTROL", "NOTIFICATIONS", "UNREGISTER", channel.id]);
|
||||
// controlApi?.send(["CONTROL", "NOTIFICATIONS", "UNREGISTER", channel.id]);
|
||||
};
|
||||
|
||||
const [testing, setTesting] = useState(false);
|
||||
@ -48,18 +49,18 @@ export default function NtfyNotificationSettings() {
|
||||
setTesting(true);
|
||||
|
||||
const events: NostrEvent[] = [];
|
||||
await new Promise<void>((res) => {
|
||||
const sub = bakery?.subscribe([{ kinds: [kinds.EncryptedDirectMessage], limit: 10, "#p": [account.pubkey] }], {
|
||||
onevent: (event) => {
|
||||
events.push(event);
|
||||
},
|
||||
oneose: () => {
|
||||
const random = events[Math.round((events.length - 1) * Math.random())];
|
||||
controlApi?.send(["CONTROL", "NOTIFICATIONS", "NOTIFY", random.id]);
|
||||
res();
|
||||
},
|
||||
});
|
||||
});
|
||||
// await new Promise<void>((res) => {
|
||||
// const sub = bakery?.subscribe([{ kinds: [kinds.EncryptedDirectMessage], limit: 10, "#p": [account.pubkey] }], {
|
||||
// onevent: (event) => {
|
||||
// events.push(event);
|
||||
// },
|
||||
// oneose: () => {
|
||||
// const random = events[Math.round((events.length - 1) * Math.random())];
|
||||
// controlApi?.send(["CONTROL", "NOTIFICATIONS", "NOTIFY", random.id]);
|
||||
// res();
|
||||
// },
|
||||
// });
|
||||
// });
|
||||
|
||||
setTesting(false);
|
||||
};
|
||||
|
@ -1,10 +1,9 @@
|
||||
import { ReactNode } from "react";
|
||||
import { Badge, Button, Flex, Heading, Text } from "@chakra-ui/react";
|
||||
import { NotificationChannel } from "@satellite-earth/core/types/control-api/notifications.js";
|
||||
import { useObservable } from "applesauce-react/hooks";
|
||||
|
||||
import { controlApi$ } from "../../../../services/bakery";
|
||||
import useNotificationChannelsReport from "../../../../hooks/reports/use-notification-channels";
|
||||
import { useObservable } from "applesauce-react/hooks";
|
||||
|
||||
function Channel({ channel }: { channel: NotificationChannel }) {
|
||||
const controlApi = useObservable(controlApi$);
|
||||
@ -30,7 +29,7 @@ function Channel({ channel }: { channel: NotificationChannel }) {
|
||||
ml="auto"
|
||||
size="xs"
|
||||
colorScheme="red"
|
||||
onClick={() => controlApi?.send(["CONTROL", "NOTIFICATIONS", "UNREGISTER", channel.id])}
|
||||
// onClick={() => controlApi?.send(["CONTROL", "NOTIFICATIONS", "UNREGISTER", channel.id])}
|
||||
>
|
||||
Remove
|
||||
</Button>
|
||||
@ -41,7 +40,7 @@ function Channel({ channel }: { channel: NotificationChannel }) {
|
||||
}
|
||||
|
||||
export default function OtherSubscriptions() {
|
||||
const { channels, report } = useNotificationChannelsReport();
|
||||
const channels: Record<string, NotificationChannel> = {};
|
||||
|
||||
if (!channels || Object.keys(channels).length === 0) return null;
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { Alert, AlertIcon, Button, Code, Flex, Heading, Link, Text, useToast } from "@chakra-ui/react";
|
||||
import { Alert, AlertIcon, Button, Code, Flex, Heading, useToast } from "@chakra-ui/react";
|
||||
import { useObservable } from "applesauce-react/hooks";
|
||||
|
||||
import { serviceWorkerRegistration } from "../../../../services/worker";
|
||||
@ -76,7 +76,7 @@ function WebPushNotificationStatus() {
|
||||
export default function WebPushNotificationSettings() {
|
||||
const controlApi = useObservable(controlApi$);
|
||||
useEffect(() => {
|
||||
controlApi?.send(["CONTROL", "NOTIFICATIONS", "GET-VAPID-KEY"]);
|
||||
// controlApi?.send(["CONTROL", "NOTIFICATIONS", "GET-VAPID-KEY"]);
|
||||
}, [controlApi]);
|
||||
|
||||
return (
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
@ -16,18 +16,17 @@ import {
|
||||
import Convert from "ansi-to-html";
|
||||
import { useObservable } from "applesauce-react/hooks";
|
||||
|
||||
import useLogsReport from "../../../../hooks/reports/use-logs-report";
|
||||
import Timestamp from "../../../../components/timestamp";
|
||||
import SimpleView from "../../../../components/layout/presets/simple-view";
|
||||
import { controlApi$ } from "../../../../services/bakery";
|
||||
import ServicesTree from "./service-tree";
|
||||
import useBakeryControl from "../../../../hooks/use-bakery-control";
|
||||
|
||||
const convert = new Convert();
|
||||
|
||||
export default function BakeryServiceLogsView() {
|
||||
const controlApi = useObservable(controlApi$);
|
||||
const control = useBakeryControl();
|
||||
const [service, setService] = useState<string | undefined>(undefined);
|
||||
const { report, logs } = useLogsReport(service);
|
||||
const logs = useObservable(control?.logs({ service })) ?? [];
|
||||
const raw = useDisclosure();
|
||||
const drawer = useDisclosure();
|
||||
|
||||
@ -55,16 +54,6 @@ export default function BakeryServiceLogsView() {
|
||||
<Button onClick={drawer.onOpen} hideFrom="2xl">
|
||||
Select Service
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (controlApi) {
|
||||
controlApi?.send(service ? ["CONTROL", "LOGS", "CLEAR", service] : ["CONTROL", "LOGS", "CLEAR"]);
|
||||
report?.clear();
|
||||
}
|
||||
}}
|
||||
>
|
||||
Clear
|
||||
</Button>
|
||||
<Spacer />
|
||||
<Switch isChecked={raw.isOpen} onChange={raw.onToggle}>
|
||||
Show Raw
|
||||
|
@ -1,6 +1,8 @@
|
||||
import { Button, ButtonGroup, Flex, FlexProps, IconButton, useDisclosure } from "@chakra-ui/react";
|
||||
import useServicesReport from "../../../../hooks/reports/use-services-report";
|
||||
import { useObservable } from "applesauce-react/hooks";
|
||||
|
||||
import { ChevronDownIcon, ChevronRightIcon } from "../../../../components/icons";
|
||||
import useBakeryControl from "../../../../hooks/use-bakery-control";
|
||||
|
||||
type Service = {
|
||||
id: string;
|
||||
@ -74,11 +76,12 @@ export default function ServicesTree({
|
||||
selected,
|
||||
...props
|
||||
}: Omit<FlexProps, "children"> & { select: (service: string) => void; selected?: string }) {
|
||||
const services = useServicesReport() ?? [];
|
||||
const control = useBakeryControl();
|
||||
const services = useObservable(control?.services) ?? [];
|
||||
|
||||
const servicesById = new Map<string, Service>();
|
||||
for (const service of services) {
|
||||
getOrCreate(servicesById, service.id.split(":"));
|
||||
getOrCreate(servicesById, service.split(":"));
|
||||
}
|
||||
|
||||
const rootServices = Array.from(servicesById.values()).filter((service) => !service.parent);
|
||||
|
@ -66,11 +66,8 @@ export default function BakerySetupView() {
|
||||
}
|
||||
|
||||
if (!pubkey) throw new Error("Unable to find nostr public key");
|
||||
if (!authParam.value) throw new Error("Missing auth code");
|
||||
|
||||
await bakery.authenticate(authParam.value);
|
||||
|
||||
controlApi.send(["CONTROL", "CONFIG", "SET", "owner", pubkey]);
|
||||
controlApi.setConfigField("owner", pubkey);
|
||||
} catch (error) {
|
||||
if (error instanceof Error) toast({ status: "error", description: error.message });
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user