Skip to content

Commit 8cbcf1f

Browse files
authored
Merge pull request #29 from planetary-social/vanish_requests
Vanish requests
2 parents 4f53fe6 + 2c54a09 commit 8cbcf1f

10 files changed

+318
-37
lines changed

config/default.js

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export default {
2020
slackCron: process.env.SLACK_CRON || "*/10 * * * *",
2121
redis: {
2222
host: process.env.REDIS_HOST || "localhost",
23+
remote_host: process.env.REDIS_REMOTE_HOST || "redis://redis:6379",
2324
},
2425
logLevel: "info",
2526
rootDomain: process.env.ROOT_DOMAIN || "nos.social",

docker-compose.yml

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
---
2-
version: "3.8"
3-
41
services:
52
server:
63
build: .
@@ -10,6 +7,7 @@ services:
107
- NODE_ENV=development
118
- REDIS_HOST=redis
129
- ROOT_DOMAIN=localhost
10+
1311
redis:
1412
image: redis:7.2.4
1513
ports:

scripts/add_name

+9-7
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ usage() {
66
echo " NPUB - The public key."
77
echo " RELAY_URLS - One or more relay URLs, each as a separate argument."
88
echo " Note: This script requires the 'pubhex' secret to be set in the NIP05_SEC environment variable."
9+
echo " The base URL can be changed by setting the BASE_URL environment variable. Default is 'https://nos.social'."
910
echo "Dependencies:"
1011
echo " nostrkeytool - A tool for NOSTR keys, installable via 'cargo install nostrkeytool' (https://crates.io/crates/nostrkeytool)."
1112
echo " nak - A tool required for authentication, installable via 'go install github.com/fiatjaf/nak@latest' (https://github.com/fiatjaf/nak)."
@@ -23,26 +24,27 @@ fi
2324
NAME="$1"
2425
NPUB="$2"
2526
RELAYS="${@:3}"
26-
27+
BASE_URL="${BASE_URL:-https://nos.social}"
2728
RELAYS_JSON_ARRAY=$(printf "%s\n" $RELAYS | jq -R . | jq -s .)
28-
BASE64_DELETE_AUTH_EVENT=$(nak event --content='' --kind 27235 -t method='DELETE' -t u="https://nos.social/api/names/$NAME" --sec $NIP05_SEC | base64)
29+
BASE64_DELETE_AUTH_EVENT=$(nak event --content='' --kind 27235 -t method='DELETE' -t u="$BASE_URL/api/names/$NAME" --sec "$NIP05_SEC" | base64)
2930

30-
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE "https://nos.social/api/names/$NAME" \
31+
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE "$BASE_URL/api/names/$NAME" \
3132
-H "Content-Type: application/json" \
3233
-H "Authorization: Nostr $BASE64_DELETE_AUTH_EVENT")
3334

3435
echo "HTTP Status from delete: $HTTP_STATUS"
3536

36-
PUBKEY=$(nostrkeytool --npub2pubkey $NPUB)
37+
PUBKEY=$(nostrkeytool --npub2pubkey "$NPUB")
3738

3839
JSON_PAYLOAD=$(jq -n \
3940
--arg name "$NAME" \
4041
--arg pubkey "$PUBKEY" \
4142
--argjson relays "$RELAYS_JSON_ARRAY" \
4243
'{name: $name, data: {pubkey: $pubkey, relays: $relays}}')
4344

44-
BASE64_AUTH_EVENT=$(nak event --content='' --kind 27235 -t method='POST' -t u='https://nos.social/api/names' --sec $NIP05_SEC | base64)
45-
curl -s https://nos.social/api/names \
45+
BASE64_AUTH_EVENT=$(nak event --content='' --kind 27235 -t method='POST' -t u="$BASE_URL/api/names" --sec "$NIP05_SEC" | base64)
46+
47+
curl -s "$BASE_URL/api/names" \
4648
-H "Content-Type: application/json" \
4749
-H "Authorization: Nostr $BASE64_AUTH_EVENT" \
48-
-d "$JSON_PAYLOAD"
50+
-d "$JSON_PAYLOAD"

src/app.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ import pinoHTTP from "pino-http";
44
import promClient from "prom-client";
55
import promBundle from "express-prom-bundle";
66
import cors from "cors";
7-
import getRedisClient from "./getRedisClient.js";
7+
import { getNip05RedisClient } from "./getRedisClient.js";
88
import routes from "./routes.js";
99
import logger from "./logger.js";
1010
import NameRecordRepository from "./nameRecordRepository.js";
1111
import fetchAndSendLatestEntries from "./slackNotifier.js";
1212
import config from "../config/index.js";
1313

14-
const redisClient = await getRedisClient();
14+
const redisClient = await getNip05RedisClient();
1515
const nameRecordRepository = new NameRecordRepository(redisClient);
1616
const app = express();
1717

src/getRedisClient.js

+32-10
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ import config from "../config/index.js";
22
import logger from "./logger.js";
33

44
// istanbul ignore next
5-
const redisImportPromise = process.env.NODE_ENV === "test"
6-
? import("ioredis-mock")
7-
: import("ioredis");
5+
const redisImportPromise =
6+
process.env.NODE_ENV === "test" ? import("ioredis-mock") : import("ioredis");
87

98
let redisClient;
9+
let remoteRedisClient;
1010

11-
async function initializeRedis() {
11+
async function initializeNip05Redis() {
1212
try {
1313
const Redis = (await redisImportPromise).default;
1414
redisClient = new Redis({
@@ -17,19 +17,41 @@ async function initializeRedis() {
1717
db: config.redis.db,
1818
});
1919

20-
redisClient.on("connect", () => logger.info("Connected to Redis"));
21-
redisClient.on("error", (err) => logger.error(err, "Redis error"));
20+
redisClient.on("connect", () => logger.info("Connected to Nip 05 Redis"));
21+
redisClient.on("error", (err) => logger.error(err, "Nip 05 Redis error"));
2222
} catch (error) {
2323
// istanbul ignore next
24-
logger.error(error, "Error initializing Redis client");
24+
logger.error(error, "Error initializing Nip 05 Redis client");
2525
}
2626
}
2727

28-
async function getRedisClient() {
28+
async function initializeVanishRequestsRedis() {
29+
try {
30+
const Redis = (await redisImportPromise).default;
31+
remoteRedisClient = new Redis(config.redis.remote_host);
32+
33+
remoteRedisClient.on("connect", () =>
34+
logger.info("Connected to vanish requests Redis")
35+
);
36+
remoteRedisClient.on("error", (err) =>
37+
logger.error(err, "Vanish requests Redis error")
38+
);
39+
} catch (error) {
40+
// istanbul ignore next
41+
logger.error(error, "Error initializing vanish requests Redis client");
42+
}
43+
}
44+
45+
export async function getNip05RedisClient() {
2946
if (!redisClient) {
30-
await initializeRedis();
47+
await initializeNip05Redis();
3148
}
3249
return redisClient;
3350
}
3451

35-
export default getRedisClient;
52+
export async function getVanishRequestsRedisClient() {
53+
if (!remoteRedisClient) {
54+
await initializeVanishRequestsRedis();
55+
}
56+
return remoteRedisClient;
57+
}

src/nameRecordRepository.js

+64-2
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ export default class NameRecordRepository {
1111
const luaScript = `
1212
local pubkey = redis.call('GET', 'pubkey:' .. KEYS[1])
1313
if not pubkey then return nil end
14-
14+
1515
local relays = redis.call('SMEMBERS', 'relays:' .. pubkey)
1616
local userAgent = redis.call('GET', 'user_agent:' .. pubkey)
1717
local clientIp = redis.call('GET', 'ip:' .. pubkey)
1818
local updatedAt = redis.call('GET', 'updated_at:' .. pubkey)
19-
19+
2020
return {pubkey, relays, userAgent, clientIp, updatedAt}
2121
`;
2222

@@ -87,6 +87,68 @@ export default class NameRecordRepository {
8787
return true;
8888
}
8989

90+
async deleteByPubkey(pubkey) {
91+
const namesToDelete = [];
92+
93+
// Use SCAN, avoid KEYS
94+
const stream = this.redis.scanStream({
95+
match: "pubkey:*",
96+
count: 1000,
97+
});
98+
99+
return new Promise((resolve, reject) => {
100+
stream.on("data", (resultKeys) => {
101+
stream.pause();
102+
103+
const pipeline = this.redis.pipeline();
104+
105+
resultKeys.forEach((key) => {
106+
pipeline.get(key);
107+
});
108+
109+
pipeline
110+
.exec()
111+
.then((results) => {
112+
for (let i = 0; i < resultKeys.length; i++) {
113+
const key = resultKeys[i];
114+
const [err, associatedPubkey] = results[i];
115+
116+
if (err) {
117+
console.error(`Error getting value for key ${key}:`, err);
118+
continue;
119+
}
120+
121+
if (associatedPubkey === pubkey) {
122+
const name = key.split(":")[1];
123+
namesToDelete.push(name);
124+
}
125+
}
126+
127+
stream.resume();
128+
})
129+
.catch((err) => {
130+
stream.destroy();
131+
reject(err);
132+
});
133+
});
134+
135+
stream.on("end", async () => {
136+
try {
137+
for (const name of namesToDelete) {
138+
await this.deleteByName(name);
139+
}
140+
resolve(true);
141+
} catch (err) {
142+
reject(err);
143+
}
144+
});
145+
146+
stream.on("error", (err) => {
147+
reject(err);
148+
});
149+
});
150+
}
151+
90152
async fetchAndClearPendingNotifications() {
91153
const luaScript = `
92154
local entries = redis.call('ZRANGE', 'pending_notifications', 0, -1)

src/server.js

+33-8
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,48 @@
11
import app from "./app.js";
22
import logger from "./logger.js";
33
import config from "../config/index.js";
4+
import {
5+
getVanishRequestsRedisClient,
6+
getNip05RedisClient,
7+
} from "./getRedisClient.js";
8+
import VanishSubscriber from "./vanishSubscriber.js";
49

5-
app.listen(config.port, () => {
10+
const vanishRequestsRedisClient = await getVanishRequestsRedisClient();
11+
const nip05RedisClient = await getNip05RedisClient();
12+
13+
const server = app.listen(config.port, () => {
614
logger.info(`Server is running on port ${config.port}`);
715
});
816

9-
process.on("uncaughtException", (err) => {
10-
logger.fatal(err, "Uncaught exception detected");
17+
const vanishSubscriber = new VanishSubscriber(
18+
vanishRequestsRedisClient,
19+
nip05RedisClient
20+
);
21+
vanishSubscriber.run();
22+
23+
async function gracefulShutdown() {
24+
logger.info("Graceful shutdown initiated...");
25+
26+
vanishSubscriber.stop();
27+
28+
while (vanishSubscriber.isRunning) {
29+
await new Promise((resolve) => setTimeout(resolve, 100));
30+
}
31+
1132
server.close(() => {
12-
process.exit(1);
33+
logger.info("Express server closed.");
34+
process.exit(0);
1335
});
36+
}
1437

15-
setTimeout(() => {
16-
process.abort();
17-
}, 1000).unref();
18-
process.exit(1);
38+
process.on("uncaughtException", (err) => {
39+
logger.fatal(err, "Uncaught exception detected");
40+
gracefulShutdown();
1941
});
2042

2143
process.on("unhandledRejection", (reason, promise) => {
2244
logger.error(reason, "An unhandled promise rejection was detected");
2345
});
46+
47+
process.on("SIGINT", gracefulShutdown);
48+
process.on("SIGTERM", gracefulShutdown);

0 commit comments

Comments
 (0)