Skip to content

Commit 679cbc1

Browse files
authored
Merge branch 'main' into fix/dead-connections-with-subscriptions
2 parents a26ccb6 + 27d8f8a commit 679cbc1

78 files changed

Lines changed: 4734 additions & 1906 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.eslintignore

Lines changed: 0 additions & 5 deletions
This file was deleted.

.eslintrc.js

Lines changed: 0 additions & 53 deletions
This file was deleted.

.github/workflows/checks.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ jobs:
4040
cache: npm
4141
- name: Install package dependencies
4242
run: npm ci
43-
- name: Run ESLint
43+
- name: Run Biome
4444
run: npm run lint
4545
- name: Run Knip
4646
run: npm run knip
@@ -56,7 +56,7 @@ jobs:
5656
cache: npm
5757
- name: Install package dependencies
5858
run: npm ci
59-
- name: Run ESLint
59+
- name: Run build check
6060
run: npm run build:check
6161
test-units-and-cover:
6262
name: Unit Tests And Coverage

.github/workflows/pr-title.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
name: PR Title Check
2+
3+
on:
4+
pull_request:
5+
types:
6+
- opened
7+
- edited
8+
- synchronize
9+
- reopened
10+
11+
jobs:
12+
pr-title-lint:
13+
name: Lint PR title
14+
runs-on: ubuntu-latest
15+
steps:
16+
- uses: amannn/action-semantic-pull-request@v5
17+
env:
18+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
19+
with:
20+
requireScope: false
21+
subjectPattern: ^.+$
22+
subjectPatternError: PR title must follow Conventional Commits format (e.g. "feat: add feature" or "fix(scope): fix bug").

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,6 @@ dist
4343
.nostr
4444

4545
# Docker Compose overrides
46-
docker-compose.overrides.yml
46+
docker-compose.overrides.yml
47+
# Export output
48+
*.jsonl

.nycrc.json

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,26 @@
11
{
22
"all": true,
33
"cache": true,
4-
"branches": 80,
5-
"lines": 80,
6-
"functions": 80,
7-
"statements": 80,
4+
"branches": 47,
5+
"lines": 54,
6+
"functions": 48,
7+
"statements": 55,
88
"watermarks": {
99
"lines": [
10-
80,
11-
95
10+
54,
11+
80
1212
],
1313
"functions": [
14-
80,
15-
95
14+
48,
15+
80
1616
],
1717
"branches": [
18-
80,
19-
95
18+
47,
19+
80
2020
],
2121
"statements": [
22-
80,
23-
95
22+
55,
23+
80
2424
]
2525
},
2626
"extension": [

CONFIGURATION.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,4 +119,10 @@ Running `nostream` for the first time creates the settings file in `<project_roo
119119
| limits.message.ipWhitelist | List of IPs (IPv4 or IPv6) to ignore rate limits. |
120120
| limits.admissionCheck.rateLimits[].period | Rate limit period in milliseconds. |
121121
| limits.admissionCheck.rateLimits[].rate | Maximum number of admission checks during period. |
122-
| limits.admissionCheck.ipWhitelist | List of IPs (IPv4 or IPv6) to ignore rate limits. |
122+
| limits.admissionCheck.ipWhitelist | List of IPs (IPv4 or IPv6) to ignore rate limits. |
123+
| nip05.mode | NIP-05 verification mode: `enabled` requires verification, `passive` verifies without blocking, `disabled` does nothing. Defaults to `disabled`. |
124+
| nip05.verifyExpiration | Time in milliseconds before a successful NIP-05 verification expires and needs re-checking. Defaults to 604800000 (1 week). |
125+
| nip05.verifyUpdateFrequency | Minimum interval in milliseconds between re-verification attempts for a given author. Defaults to 86400000 (24 hours). |
126+
| nip05.maxConsecutiveFailures | Number of consecutive verification failures before giving up on an author. Defaults to 20. |
127+
| nip05.domainWhitelist | List of domains allowed for NIP-05 verification. If set, only authors verified at these domains can publish. |
128+
| nip05.domainBlacklist | List of domains blocked from NIP-05 verification. Authors with NIP-05 at these domains will be rejected. |

CONTRIBUTING.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ Run dead code and dependency analysis before opening a pull request:
1414
npm run knip
1515
```
1616

17-
`npm run lint` now runs Knip first, then ESLint.
17+
`npm run lint` now runs Biome.
1818

1919
## Pull Request Process
2020

@@ -24,3 +24,12 @@ npm run knip
2424
Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/).
2525
3. You may merge the Pull Request in once you have the sign-off of two other developers, or if you
2626
do not have permission to do that, you may request the second reviewer to merge it for you.
27+
28+
## Code Quality
29+
30+
Run Biome checks before opening a pull request:
31+
32+
```
33+
npm run lint
34+
npm run format:check
35+
```

README.md

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,17 @@ Start:
470470
471471
## Tests
472472
473+
### Linting and formatting (Biome)
474+
475+
Run code quality checks with Biome:
476+
477+
```
478+
npm run lint
479+
npm run lint:fix
480+
npm run format
481+
npm run format:check
482+
```
483+
473484
### Unit tests
474485
475486
Open a terminal and change to the project's directory:
@@ -570,6 +581,101 @@ To see the integration test coverage report open `.coverage/integration/lcov-rep
570581
open .coverage/integration/lcov-report/index.html
571582
```
572583
584+
585+
## Security & Load Testing
586+
587+
Nostream includes a specialized security tester to simulate Slowloris-style connection holding and event flood (spam) attacks. This is used to verify relay resilience and prevent memory leaks.
588+
589+
### Running the Tester
590+
```bash
591+
# Simulates 5,000 idle "zombie" connections + 100 events/sec spam
592+
npm run test:load -- --zombies 5000 --spam-rate 100
593+
```
594+
595+
### Analyzing Memory (Heap Snapshots)
596+
To verify that connections are being correctly evicted and memory reclaimed:
597+
1. Ensure the relay is running with `--inspect` enabled (see `docker-compose.yml`).
598+
2. Open **Chrome DevTools** (`chrome://inspect`) and connect to the relay process.
599+
3. In the **Memory** tab, take a **Heap Snapshot** (Baseline).
600+
4. Run the load tester.
601+
5. Wait for the eviction cycle (default: 120s) and take a second **Heap Snapshot**.
602+
6. Switch the view to **Comparison** and select the Baseline snapshot.
603+
7. Verify that object counts (e.g., `WebSocketAdapter`, `SocketAddress`) return to baseline levels.
604+
605+
### Server-Side Monitoring
606+
To observe client and subscription counts in real-time during a test, you can instrument `src/adapters/web-socket-server-adapter.ts`:
607+
608+
1. Locate the `onHeartbeat()` method.
609+
2. Add the following logging logic:
610+
```typescript
611+
private onHeartbeat() {
612+
let totalSubs = 0;
613+
let totalClients = 0;
614+
this.webSocketServer.clients.forEach((webSocket) => {
615+
totalClients++;
616+
const webSocketAdapter = this.webSocketsAdapters.get(webSocket) as IWebSocketAdapter;
617+
if (webSocketAdapter) {
618+
webSocketAdapter.emit(WebSocketAdapterEvent.Heartbeat);
619+
totalSubs += webSocketAdapter.getSubscriptions().size;
620+
}
621+
});
622+
console.log(`[HEARTBEAT] Clients: ${totalClients} | Total subscriptions: ${totalSubs} | Heap Used: ${(process.memoryUsage().heapUsed / 1024 / 1024).toFixed(1)} MB`);
623+
}
624+
```
625+
3. View the live output via Docker logs:
626+
```bash
627+
docker compose logs -f nostream
628+
```
629+
=======
630+
## Export Events
631+
632+
Export all stored events to a [JSON Lines](https://jsonlines.org/) (`.jsonl`) file. Each line is a valid NIP-01 Nostr event JSON object. The export streams rows from the database using cursors, so it works safely on relays with millions of events without loading them into memory.
633+
634+
```
635+
npm run export # writes to events.jsonl
636+
npm run export -- backup-2024-01-01.jsonl # custom filename
637+
```
638+
639+
The script reads the same `DB_*` environment variables used by the relay (see [CONFIGURATION.md](CONFIGURATION.md)).
640+
## Relay Maintenance
641+
642+
Use `clean-db` to wipe or prune `events` table data. This also removes
643+
corresponding data from the derived `event_tags` table when present.
644+
645+
Dry run (no deletion):
646+
647+
```
648+
npm run clean-db -- --all --dry-run
649+
```
650+
651+
Full wipe:
652+
653+
```
654+
npm run clean-db -- --all --force
655+
```
656+
657+
Delete events older than N days:
658+
659+
```
660+
npm run clean-db -- --older-than=30 --force
661+
```
662+
663+
Delete only selected kinds:
664+
665+
```
666+
npm run clean-db -- --kinds=1,7,4 --force
667+
```
668+
669+
Delete only selected kinds older than N days:
670+
671+
```
672+
npm run clean-db -- --older-than=30 --kinds=1,7,4 --force
673+
```
674+
675+
By default, the script asks for explicit confirmation (`Type 'DELETE' to confirm`).
676+
Use `--force` to skip the prompt.
677+
678+
573679
## Configuration
574680

575681
You can change the default folder by setting the `NOSTR_CONFIG_DIR` environment variable to a different path.

biome.json

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"$schema": "https://biomejs.dev/schemas/2.4.11/schema.json",
3+
"vcs": { "enabled": true, "clientKind": "git", "useIgnoreFile": true },
4+
"files": {
5+
"ignoreUnknown": false,
6+
"includes": [
7+
"**",
8+
"!**/node_modules/**",
9+
"!**/dist/**",
10+
"!**/.test-reports/**",
11+
"!**/.coverage/**",
12+
"!**/.nostr/**",
13+
"!**/tslint.json"
14+
]
15+
},
16+
"formatter": {
17+
"enabled": true,
18+
"indentStyle": "space",
19+
"lineWidth": 120
20+
},
21+
"linter": {
22+
"enabled": true,
23+
"rules": {
24+
"recommended": false,
25+
"correctness": { "noUnusedVariables": "error" },
26+
"style": { "useBlockStatements": "warn" },
27+
"suspicious": { "noExplicitAny": "off" }
28+
}
29+
},
30+
"javascript": {
31+
"formatter": {
32+
"quoteStyle": "single",
33+
"semicolons": "asNeeded",
34+
"trailingCommas": "all"
35+
}
36+
}
37+
}

0 commit comments

Comments
 (0)