Skip to content

Commit e81fb36

Browse files
committed
feat: adds incoming PDUs processing
1 parent 66e6a48 commit e81fb36

File tree

114 files changed

+7610
-1768
lines changed

Some content is hidden

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

114 files changed

+7610
-1768
lines changed

.husky/pre-commit

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
bun lint
22
bunx tsc
3-
bun test
4-
3+
bun test

bun.lock

Lines changed: 875 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bun.lockb

-64.8 KB
Binary file not shown.

index.ts

Lines changed: 27 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,30 @@
1-
// // /v2/server/{keyID}
2-
// // /v2/server/
3-
// // /v2/server
1+
import { HomeserverModule } from '@hs/homeserver/src/homeserver.module';
2+
import { HttpLoggerInterceptor } from '@hs/homeserver/src/middleware/http-logger.interceptor';
3+
import { ConfigService } from '@hs/homeserver/src/services/config.service';
4+
import { NestFactory } from '@nestjs/core';
5+
import 'reflect-metadata';
46

5-
// // /v2/query
6-
// // /v2/query/{serverName}/{keyID}
7-
// // /v1/send/{txnID}
7+
async function bootstrap() {
8+
try {
9+
const nestApp = await NestFactory.create(HomeserverModule, {
10+
logger: ['error', 'warn', 'log', 'debug'],
11+
});
12+
13+
nestApp.useGlobalInterceptors(new HttpLoggerInterceptor());
14+
15+
await nestApp.init();
816

9-
// // /v1/invite/{roomID}/{eventID}
10-
// // /v2/invite/{roomID}/{eventID}
11-
// // /v3/invite/{roomID}/{userID}
12-
// // /v1/3pid/onbind
13-
// // /v1/exchange_third_party_invite/{roomID}
14-
// // /v1/event/{eventID}
15-
// // /v1/state/{roomID}
16-
// // /v1/state_ids/{roomID}
17-
// // /v1/event_auth/{roomID}/{eventID}
18-
// // /v1/query/directory
19-
// // /v1/query/profile
20-
// // /v1/user/devices/{userID}
21-
// // /v1/peek/{roomID}/{peekID}
22-
// // /v1/make_join/{roomID}/{userID}
23-
// // /v1/send_join/{roomID}/{eventID}
24-
// // /v2/send_join/{roomID}/{eventID}
25-
// // /v1/make_leave/{roomID}/{userID}
26-
// // /v1/send_leave/{roomID}/{eventID}
27-
// // /v2/send_leave/{roomID}/{eventID}
28-
// // /v1/version
29-
// // /v1/get_missing_events/{roomID}
30-
// // /v1/backfill/{roomID}
31-
// // /v1/publicRooms
32-
// // /v1/user/keys/claim
33-
// // /v1/user/keys/query
34-
// // /v1/openid/userinfo
35-
// // /v1/hierarchy/{roomID}
17+
const configService = nestApp.get(ConfigService);
18+
19+
const port = configService.getServerConfig().port;
20+
const host = configService.getServerConfig().host;
21+
22+
nestApp.listen(port, () => {
23+
console.log(`🚀 App running on http://${host}:${port}`);
24+
});
25+
} catch (error) {
26+
console.error('Error setting up the application:', error);
27+
}
28+
}
3629

37-
import Elysia from "elysia";
38-
import { app } from "@hs/homeserver";
39-
import { fakeEndpoints } from "@hs/fake";
40-
import { routerWithMongodb } from "@hs/homeserver/src/plugins/mongodb";
41-
42-
import { config } from "./config";
43-
import { db } from "./mongo";
44-
45-
new Elysia({
46-
handler: {
47-
standardHostname: false,
48-
},
49-
})
50-
.decorate("config", config)
51-
.use(routerWithMongodb(db))
52-
.use(app)
53-
.use(fakeEndpoints)
54-
.listen(config.port, (context) => {
55-
console.log(
56-
`🦊 Homeserver is running at http://${context.hostname}:${context.port}`,
57-
);
58-
});
30+
bootstrap();

package.json

Lines changed: 44 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,45 @@
11
{
2-
"name": "homeserver",
3-
"module": "index.ts",
4-
"private": true,
5-
"type": "module",
6-
"devDependencies": {
7-
"@biomejs/biome": "^1.9.4",
8-
"@types/bun": "latest",
9-
"husky": "^9.1.7"
10-
},
11-
"peerDependencies": {
12-
"typescript": "^5.0.0"
13-
},
14-
"workspaces": ["packages/*"],
15-
"dependencies": {
16-
"@bogeychan/elysia-etag": "^0.0.6",
17-
"@bogeychan/elysia-logger": "^0.1.4",
18-
"@elysiajs/swagger": "^1.1.6",
19-
"@hs/fake": "workspace:*",
20-
"@hs/homeserver": "workspace:*",
21-
"bun-bagel": "^1.1.0",
22-
"elysia": "^1.1.26",
23-
"mongodb": "^6.11.0",
24-
"node-jsonwebtoken": "^0.0.1",
25-
"tweetnacl": "^1.0.3"
26-
},
27-
"husky": {
28-
"hooks": {
29-
"pre-commit": "bun test"
30-
}
31-
},
32-
"scripts": {
33-
"prepare": "husky",
34-
"start": "bun run index.ts",
35-
"test": "bun test",
36-
"test:coverage": "bun test --coverage",
37-
"lint": "bunx @biomejs/biome lint",
38-
"tsc": "bunx tsc"
39-
}
40-
}
2+
"name": "homeserver",
3+
"module": "index.ts",
4+
"private": true,
5+
"type": "module",
6+
"devDependencies": {
7+
"@biomejs/biome": "^1.9.4",
8+
"@types/bun": "latest",
9+
"husky": "^9.1.7"
10+
},
11+
"peerDependencies": {
12+
"typescript": "^5.0.0"
13+
},
14+
"workspaces": ["packages/*"],
15+
"dependencies": {
16+
"@bogeychan/elysia-etag": "^0.0.6",
17+
"@bogeychan/elysia-logger": "^0.1.4",
18+
"@elysiajs/swagger": "^1.1.6",
19+
"@hs/fake": "workspace:*",
20+
"@hs/homeserver": "workspace:*",
21+
"@nestjs/common": "^11.1.0",
22+
"@nestjs/core": "^11.1.0",
23+
"bun-bagel": "^1.1.0",
24+
"dotenv": "^16.5.0",
25+
"elysia": "^1.1.26",
26+
"mongodb": "^6.11.0",
27+
"node-jsonwebtoken": "^0.0.1",
28+
"reflect-metadata": "^0.2.2",
29+
"tweetnacl": "^1.0.3",
30+
"zod": "^3.24.3"
31+
},
32+
"husky": {
33+
"hooks": {
34+
"pre-commit": "bun test"
35+
}
36+
},
37+
"scripts": {
38+
"prepare": "husky",
39+
"start": "bun run index.ts",
40+
"test": "bun test",
41+
"test:coverage": "bun test --coverage",
42+
"lint": "bunx @biomejs/biome lint",
43+
"tsc": "bunx tsc"
44+
}
45+
}

packages/federation-sdk/README.md

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
# Matrix Federation SDK
2+
3+
A simple SDK for Matrix server-to-server communication. This SDK provides a clean interface for making Matrix federation API calls, handling the complex authentication and request signing required by the Matrix specification.
4+
5+
## Features
6+
7+
- Simple, Promise-based API for all Matrix federation endpoints
8+
- Automatic request signing and authentication
9+
- DNS-based server discovery
10+
- Typed responses using Zod schemas
11+
- Comprehensive error handling
12+
13+
## Installation
14+
15+
```bash
16+
# From project root
17+
cd homeserver/packages/federation-sdk
18+
npm install
19+
npm run build
20+
```
21+
22+
## Usage
23+
24+
### Basic Setup
25+
26+
```typescript
27+
import { FederationClient } from '@hs/federation-sdk';
28+
import { generateKeyPairsFromString } from '../../homeserver/src/keys';
29+
30+
// Create signing key
31+
const signingKey = await generateKeyPairsFromString(
32+
"ed25519 myKey yourPrivateKeyHere"
33+
);
34+
35+
// Create federation client
36+
const client = new FederationClient({
37+
serverName: "your-server.com",
38+
signingKey: signingKey,
39+
debug: true // Optional, enables detailed logging
40+
});
41+
```
42+
43+
### Getting Server Version
44+
45+
```typescript
46+
const targetServer = "matrix.org";
47+
48+
try {
49+
const version = await client.getVersion(targetServer);
50+
console.log(`Server version: ${version.server.name} ${version.server.version}`);
51+
} catch (error) {
52+
console.error(`Error getting version: ${error}`);
53+
}
54+
```
55+
56+
### Querying Room State
57+
58+
```typescript
59+
const targetServer = "matrix.org";
60+
const roomId = "!someRoomId:matrix.org";
61+
62+
try {
63+
// First get room version
64+
const roomVersion = await client.getRoomVersion(targetServer, roomId);
65+
console.log(`Room version: ${roomVersion}`);
66+
67+
// Get state IDs
68+
const stateIds = await client.getStateIds(targetServer, roomId);
69+
console.log(`Room has ${stateIds.pdu_ids.length} state events`);
70+
71+
if (stateIds.pdu_ids.length > 0) {
72+
// Use an event ID to get room state (required by most servers)
73+
const eventId = stateIds.pdu_ids[0];
74+
75+
// Get full state using a reference event ID
76+
const state = await client.getState(targetServer, roomId, undefined, undefined, eventId);
77+
console.log(`Got ${state.pdus.length} state events`);
78+
79+
// Get specific state type
80+
const powerLevels = await client.getState(
81+
targetServer,
82+
roomId,
83+
"m.room.power_levels",
84+
"",
85+
eventId
86+
);
87+
console.log("Power levels:", powerLevels);
88+
}
89+
} catch (error) {
90+
console.error(`Error querying room: ${error}`);
91+
}
92+
```
93+
94+
### Sending Events
95+
96+
```typescript
97+
const targetServer = "matrix.org";
98+
const event = {
99+
type: "m.room.message",
100+
room_id: "!someRoomId:matrix.org",
101+
sender: "@youruser:your-server.com",
102+
content: {
103+
msgtype: "m.text",
104+
body: "Hello from Federation SDK!"
105+
},
106+
// other required fields
107+
};
108+
109+
try {
110+
const response = await client.sendEvent(targetServer, event);
111+
console.log("Event sent successfully:", response);
112+
} catch (error) {
113+
console.error(`Error sending event: ${error}`);
114+
}
115+
```
116+
117+
### Room Joining Flow
118+
119+
```typescript
120+
const targetServer = "matrix.org";
121+
const roomId = "!someRoomId:matrix.org";
122+
const userId = "@youruser:your-server.com";
123+
124+
try {
125+
// 1. Prepare to join
126+
const makeJoinResponse = await client.makeJoin(targetServer, roomId, userId);
127+
console.log(`Got make_join response with room version ${makeJoinResponse.room_version}`);
128+
129+
// 2. Complete the event (sign it, add hashes, etc.)
130+
const joinEvent = completeJoinEvent(makeJoinResponse.event);
131+
132+
// 3. Send the join
133+
const sendJoinResponse = await client.sendJoin(
134+
targetServer,
135+
roomId,
136+
joinEvent.event_id,
137+
joinEvent
138+
);
139+
140+
console.log(`Join successful! Got ${sendJoinResponse.state.length} state events`);
141+
} catch (error) {
142+
console.error(`Join failed: ${error}`);
143+
}
144+
```
145+
146+
## API Documentation
147+
148+
See the [Matrix Federation API Specification](https://spec.matrix.org/v1.7/server-server-api/) for more details on the underlying protocol.
149+
150+
## License
151+
152+
MIT
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import fs from 'fs';
2+
import { generateKeyPairsFromString } from '../../homeserver/src/keys';
3+
import { FederationClient } from '../src';
4+
5+
async function main() {
6+
const serverName = process.env.SERVER_NAME || 'your-server.example.com';
7+
8+
const keyString = process.env.SIGNING_KEY ||
9+
fs.readFileSync('./signing-key.txt', 'utf-8').trim();
10+
11+
try {
12+
const signingKey = await generateKeyPairsFromString(keyString);
13+
14+
const federation = new FederationClient({
15+
serverName,
16+
signingKey,
17+
debug: true
18+
});
19+
20+
const targetServer = process.env.TARGET_SERVER || 'matrix.org';
21+
console.log(`Querying server: ${targetServer}`);
22+
23+
try {
24+
console.log('Getting server version...');
25+
const version = await federation.getVersion(targetServer);
26+
console.log(`Server version: ${version.server.name} ${version.server.version}`);
27+
} catch (error) {
28+
console.error('Error getting server version:', error);
29+
}
30+
31+
const userId = process.env.USER_ID || '@alice:matrix.org';
32+
try {
33+
console.log(`Querying profile for ${userId}...`);
34+
const profile = await federation.queryProfile(targetServer, userId);
35+
console.log('Profile data:', profile);
36+
} catch (error) {
37+
console.error('Error querying profile:', error);
38+
}
39+
40+
} catch (error) {
41+
console.error('Fatal error:', error);
42+
process.exit(1);
43+
}
44+
}
45+
46+
main().catch(error => {
47+
console.error('Unhandled error:', error);
48+
process.exit(1);
49+
});

0 commit comments

Comments
 (0)