Support for the basics of federation (#1)

Reviewed-on: #1
This commit is contained in:
Steffo 2024-10-17 22:48:13 +00:00 committed by Cross
commit 72d27b0ff1
Signed by: forgejo
GPG key ID: 3277D7B12BD4777D
8 changed files with 159 additions and 51 deletions

5
.vscode/extensions.json vendored Normal file
View file

@ -0,0 +1,5 @@
{
"recommendations": [
"denoland.vscode-deno"
]
}

View file

@ -1,14 +1,35 @@
{ {
"tasks": { "imports": {
"dev": "deno run --watch src/main.ts" "@fedify/fedify": "jsr:@fedify/fedify@^1.0.2",
}, "@fedify/redis": "jsr:@fedify/redis@^0.3.0",
"imports": { "@logtape/logtape": "jsr:@logtape/logtape@^0.6.3",
"@fedify/fedify": "jsr:@fedify/fedify@^1.0.2", "@std/assert": "jsr:@std/assert@1",
"@fedify/redis": "jsr:@fedify/redis@^0.3.0", "@hongminhee/x-forwarded-fetch": "jsr:@hongminhee/x-forwarded-fetch@^0.2.0",
"@std/assert": "jsr:@std/assert@1", "@@npm/ioredis": "npm:ioredis@^5.4.1"
"ioredis": "npm:ioredis@^5.4.1" },
}, "unstable": [
"unstable": [ "temporal"
"temporal" ],
] "lint": {
} "rules": {
"exclude": [
"no-implicit-any",
"no-explicit-any",
"no-inner-declarations",
"no-var"
]
}
},
"fmt": {
"include": [
"src/**",
"tests/**"
],
"indentWidth": 4,
"lineWidth": 280,
"semiColons": false,
"proseWrap": "never",
"singleQuote": false,
"useTabs": true
}
}

View file

@ -4,15 +4,14 @@
"specifiers": { "specifiers": {
"jsr:@fedify/fedify@^1.0.2": "jsr:@fedify/fedify@1.0.2", "jsr:@fedify/fedify@^1.0.2": "jsr:@fedify/fedify@1.0.2",
"jsr:@fedify/redis@^0.3.0": "jsr:@fedify/redis@0.3.0", "jsr:@fedify/redis@^0.3.0": "jsr:@fedify/redis@0.3.0",
"jsr:@hongminhee/x-forwarded-fetch@^0.2.0": "jsr:@hongminhee/x-forwarded-fetch@0.2.0",
"jsr:@hugoalh/http-header-link@^1.0.2": "jsr:@hugoalh/http-header-link@1.0.2", "jsr:@hugoalh/http-header-link@^1.0.2": "jsr:@hugoalh/http-header-link@1.0.2",
"jsr:@hugoalh/is-string-singleline@1.0.2": "jsr:@hugoalh/is-string-singleline@1.0.2", "jsr:@hugoalh/is-string-singleline@1.0.2": "jsr:@hugoalh/is-string-singleline@1.0.2",
"jsr:@logtape/logtape@^0.6.2": "jsr:@logtape/logtape@0.6.3", "jsr:@logtape/logtape@^0.6.2": "jsr:@logtape/logtape@0.6.3",
"jsr:@logtape/logtape@^0.6.3": "jsr:@logtape/logtape@0.6.3", "jsr:@logtape/logtape@^0.6.3": "jsr:@logtape/logtape@0.6.3",
"jsr:@std/assert@1": "jsr:@std/assert@1.0.6",
"jsr:@std/bytes@^1.0.2": "jsr:@std/bytes@1.0.2", "jsr:@std/bytes@^1.0.2": "jsr:@std/bytes@1.0.2",
"jsr:@std/encoding@^1.0.5": "jsr:@std/encoding@1.0.5", "jsr:@std/encoding@^1.0.5": "jsr:@std/encoding@1.0.5",
"jsr:@std/http@^1.0.6": "jsr:@std/http@1.0.8", "jsr:@std/http@^1.0.6": "jsr:@std/http@1.0.8",
"jsr:@std/internal@^1.0.4": "jsr:@std/internal@1.0.4",
"jsr:@std/semver@^1.0.3": "jsr:@std/semver@1.0.3", "jsr:@std/semver@^1.0.3": "jsr:@std/semver@1.0.3",
"npm:@phensley/language-tag@^1.9.0": "npm:@phensley/language-tag@1.9.0", "npm:@phensley/language-tag@^1.9.0": "npm:@phensley/language-tag@1.9.0",
"npm:asn1js@^3.0.5": "npm:asn1js@3.0.5", "npm:asn1js@^3.0.5": "npm:asn1js@3.0.5",
@ -52,6 +51,9 @@
"jsr:@logtape/logtape@^0.6.3" "jsr:@logtape/logtape@^0.6.3"
] ]
}, },
"@hongminhee/x-forwarded-fetch@0.2.0": {
"integrity": "8a347e061974e07b480e9461c7d84047e12e92c462fe7679a6f0f59b5c5f48d5"
},
"@hugoalh/http-header-link@1.0.2": { "@hugoalh/http-header-link@1.0.2": {
"integrity": "1f607e34ac0790a0b0759f89ade294ab3a1d211e46a8dea337eaafa26950205f", "integrity": "1f607e34ac0790a0b0759f89ade294ab3a1d211e46a8dea337eaafa26950205f",
"dependencies": [ "dependencies": [
@ -64,12 +66,6 @@
"@logtape/logtape@0.6.3": { "@logtape/logtape@0.6.3": {
"integrity": "64cac3459fddf0455b85d36c8ca3e21764d6b2965c426fb40a0e4a98be436d2b" "integrity": "64cac3459fddf0455b85d36c8ca3e21764d6b2965c426fb40a0e4a98be436d2b"
}, },
"@std/assert@1.0.6": {
"integrity": "1904c05806a25d94fe791d6d883b685c9e2dcd60e4f9fc30f4fc5cf010c72207",
"dependencies": [
"jsr:@std/internal@^1.0.4"
]
},
"@std/bytes@1.0.2": { "@std/bytes@1.0.2": {
"integrity": "fbdee322bbd8c599a6af186a1603b3355e59a5fb1baa139f8f4c3c9a1b3e3d57" "integrity": "fbdee322bbd8c599a6af186a1603b3355e59a5fb1baa139f8f4c3c9a1b3e3d57"
}, },
@ -79,9 +75,6 @@
"@std/http@1.0.8": { "@std/http@1.0.8": {
"integrity": "6ea1b2e8d33929967754a3b6d6c6f399ad6647d7bbb5a466c1eaf9b294a6ebcd" "integrity": "6ea1b2e8d33929967754a3b6d6c6f399ad6647d7bbb5a466c1eaf9b294a6ebcd"
}, },
"@std/internal@1.0.4": {
"integrity": "62e8e4911527e5e4f307741a795c0b0a9e6958d0b3790716ae71ce085f755422"
},
"@std/semver@1.0.3": { "@std/semver@1.0.3": {
"integrity": "7c139c6076a080eeaa4252c78b95ca5302818d7eafab0470d34cafd9930c13c8" "integrity": "7c139c6076a080eeaa4252c78b95ca5302818d7eafab0470d34cafd9930c13c8"
} }
@ -348,6 +341,8 @@
"dependencies": [ "dependencies": [
"jsr:@fedify/fedify@^1.0.2", "jsr:@fedify/fedify@^1.0.2",
"jsr:@fedify/redis@^0.3.0", "jsr:@fedify/redis@^0.3.0",
"jsr:@hongminhee/x-forwarded-fetch@^0.2.0",
"jsr:@logtape/logtape@^0.6.3",
"jsr:@std/assert@1", "jsr:@std/assert@1",
"npm:ioredis@^5.4.1" "npm:ioredis@^5.4.1"
] ]

42
src/federation.ts Normal file
View file

@ -0,0 +1,42 @@
import { createFederation, Follow, Service } from "@fedify/fedify"
import { kv } from "./redis.ts"
import { getLogger } from "https://jsr.io/@logtape/logtape/0.6.3/logtape/logger.ts"
const l = getLogger(["dotino-veloce", "federation"])
l.debug`Creating federation object...`
export const federation = createFederation<void>({ kv })
l.debug`Creating actor dispatcher...`
// deno-lint-ignore require-await
async function actorDispatcher(ctx: any, handle: string) {
l.debug`Received request for actor ${handle}, handling...`
if (handle !== "service") {
l.debug`No match found for ${handle}, returning null.`
return null
}
l.debug`Determining id of actor ${handle}...`
const id = ctx.getActorUri(handle)
l.info`Returning actor: ${id.href}`
return new Service({
id,
name: "[TEST] Dotino Veloce",
summary: "Core account of a Dotino Veloce instance.",
preferredUsername: id.href,
// Akkoma expects URL to be equal to ID
// https://akkoma.dev/AkkomaGang/akkoma/src/commit/f1018867097e6f293d8b2b5b6935f0a7ebf99bd0/lib/pleroma/object/fetcher.ex#L287
url: id,
inbox: ctx.getInboxUri(handle),
})
}
l.debug`Connecting actor dispatcher to federation object...`
federation.setActorDispatcher("/users/{identifier}", actorDispatcher)
// Akkoma requires inboxes to be setup to display profiles
// https://akkoma.dev/AkkomaGang/akkoma/src/commit/f1018867097e6f293d8b2b5b6935f0a7ebf99bd0/lib/pleroma/web/activity_pub/object_validators/user_validator.ex#L72
l.debug`Initializing inbox listener...`
federation.setInboxListeners("/inbox/{identifier}") // I don't really care about the shared inbox for this project

40
src/handler.ts Normal file
View file

@ -0,0 +1,40 @@
import { getLogger } from "@logtape/logtape"
import { behindProxy, Fetch } from "@hongminhee/x-forwarded-fetch"
import { federation } from "./federation.ts"
const l = getLogger(["dotino-veloce", "handler"])
l.debug`Creating Deno handler...`
function handler(request: Request, _info: Deno.ServeHandlerInfo) {
l.debug`Received a request, processing...`
const agent = request.headers.get("User-Agent")
const requestUrl = new URL(request.url)
l.debug`Received request from ${agent} to ${requestUrl.href}`
// Akkoma expects host-meta to be correctly setup
// https://akkoma.dev/AkkomaGang/akkoma/src/commit/f1018867097e6f293d8b2b5b6935f0a7ebf99bd0/lib/pleroma/web/web_finger.ex#L177
l.debug`Routing request to: ${requestUrl.pathname}`
if (requestUrl.pathname === "/.well-known/host-meta") {
l.debug`Intercepting request to inject host-meta for ${requestUrl.origin}`
return new Response(
`<?xml version="1.0" encoding="UTF-8"?><XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0"><Link type="application/xrd+xml" template="${requestUrl.origin}/.well-known/webfinger?resource={uri}" rel="lrdd" /></XRD>`,
{
headers: {
"Content-Type": "application/xml",
},
},
)
}
l.debug`Delegating request to Federation...`
return federation.fetch(
request,
{
contextData: undefined,
},
)
}
l.debug`Creating proxyied Deno handler...`
export const proxyHandler: Deno.ServeHandler = behindProxy(handler as Fetch) // Should be good.

View file

@ -1,30 +1,24 @@
import {createFederation} from "@fedify/fedify" import { configure, getConsoleSink, getLogger } from "@logtape/logtape"
import {RedisKvStore} from "https://jsr.io/@fedify/redis/0.3.0/src/kv.ts" import { proxyHandler } from "./handler.ts"
import {Redis} from "ioredis";
await configure({
sinks: { console: getConsoleSink() },
filters: {},
loggers: [
{ category: ["logtape", "meta"], sinks: ["console"], level: "warning" },
{ category: ["fedify"], sinks: ["console"], level: "info" },
{ category: ["dotino-veloce"], sinks: ["console"], level: "debug" },
],
})
const federation = createFederation<void>({ const l = getLogger(["dotino-veloce", "main"])
kv: new RedisKvStore(
new Redis({}),
{}
),
});
const handler: Deno.ServeHandler = function handler(request) {
const response = federation.fetch(
request,
{
contextData: undefined,
}
)
return response
}
l.info`Starting server...`
Deno.serve( Deno.serve(
{ {
port: 8080 port: 8080,
onListen: (_localAddr) => {
},
}, },
handler proxyHandler,
) )

11
src/redis.ts Normal file
View file

@ -0,0 +1,11 @@
import { RedisKvStore } from "@fedify/redis"
import { getLogger } from "@logtape/logtape"
import { Redis } from "@@npm/ioredis"
const l = getLogger(["dotino-veloce", "redis"])
l.debug`Creating redis object...`
export const redis = new Redis({})
l.debug`Creating federation object...`
export const kv = new RedisKvStore(redis, {})

View file

@ -1,6 +1,6 @@
import { assertEquals } from "@std/assert"; import { assertEquals } from "@std/assert"
import { add } from "../src/main.ts"; import { add } from "../src/main.ts"
Deno.test(function addTest() { Deno.test(function addTest() {
assertEquals(add(2, 3), 5); assertEquals(add(2, 3), 5)
}); })