mirror of
https://github.com/versia-pub/server.git
synced 2025-12-06 16:38:19 +01:00
refactor(federation): 🔥 Remove old code and simplify federation requests
This commit is contained in:
parent
ad9ed2598c
commit
2f823317c2
|
|
@ -1,27 +0,0 @@
|
||||||
import { emojiValidatorWithColons } from "@/api";
|
|
||||||
import { type InferSelectModel, inArray } from "drizzle-orm";
|
|
||||||
import { Emojis, type Instances } from "~/drizzle/schema";
|
|
||||||
import { Emoji } from "~/packages/database-interface/emoji";
|
|
||||||
|
|
||||||
export type EmojiWithInstance = InferSelectModel<typeof Emojis> & {
|
|
||||||
instance: InferSelectModel<typeof Instances> | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used for parsing emojis from local text
|
|
||||||
* @param text The text to parse
|
|
||||||
* @returns An array of emojis
|
|
||||||
*/
|
|
||||||
export const parseEmojis = async (text: string): Promise<Emoji[]> => {
|
|
||||||
const matches = text.match(emojiValidatorWithColons);
|
|
||||||
if (!matches || matches.length === 0) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return Emoji.manyFromSql(
|
|
||||||
inArray(
|
|
||||||
Emojis.shortcode,
|
|
||||||
matches.map((match) => match.replace(/:/g, "")),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
@ -1,65 +1,10 @@
|
||||||
import { debugRequest } from "@/api";
|
import type { Undo } from "@lysand-org/federation/types";
|
||||||
import { getLogger } from "@logtape/logtape";
|
|
||||||
import { SignatureConstructor } from "@lysand-org/federation";
|
|
||||||
import type { Entity, Undo } from "@lysand-org/federation/types";
|
|
||||||
import { config } from "config-manager";
|
import { config } from "config-manager";
|
||||||
import type { User } from "~/packages/database-interface/user";
|
import type { User } from "~/packages/database-interface/user";
|
||||||
|
|
||||||
export const localObjectUri = (id: string) =>
|
export const localObjectUri = (id: string) =>
|
||||||
new URL(`/objects/${id}`, config.http.base_url).toString();
|
new URL(`/objects/${id}`, config.http.base_url).toString();
|
||||||
|
|
||||||
export const objectToInboxRequest = async (
|
|
||||||
object: Entity,
|
|
||||||
author: User,
|
|
||||||
userToSendTo: User,
|
|
||||||
): Promise<Request> => {
|
|
||||||
if (userToSendTo.isLocal() || !userToSendTo.data.endpoints?.inbox) {
|
|
||||||
throw new Error("UserToSendTo has no inbox or is a local user");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (author.isRemote()) {
|
|
||||||
throw new Error("Author is a remote user");
|
|
||||||
}
|
|
||||||
|
|
||||||
const privateKey = await crypto.subtle.importKey(
|
|
||||||
"pkcs8",
|
|
||||||
Buffer.from(author.data.privateKey ?? "", "base64"),
|
|
||||||
"Ed25519",
|
|
||||||
false,
|
|
||||||
["sign"],
|
|
||||||
);
|
|
||||||
|
|
||||||
const ctor = new SignatureConstructor(privateKey, author.getUri());
|
|
||||||
|
|
||||||
const userInbox = new URL(userToSendTo.data.endpoints?.inbox ?? "");
|
|
||||||
|
|
||||||
const request = new Request(userInbox, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Origin: new URL(config.http.base_url).host,
|
|
||||||
},
|
|
||||||
body: JSON.stringify(object),
|
|
||||||
});
|
|
||||||
|
|
||||||
const { request: signed, signedString } = await ctor.sign(request);
|
|
||||||
|
|
||||||
if (config.debug.federation) {
|
|
||||||
// Debug request
|
|
||||||
await debugRequest(signed);
|
|
||||||
|
|
||||||
const logger = getLogger("federation");
|
|
||||||
|
|
||||||
// Log public key
|
|
||||||
logger.debug`Sender public key: ${author.data.publicKey}`;
|
|
||||||
|
|
||||||
// Log signed string
|
|
||||||
logger.debug`Signed string:\n${signedString}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return signed;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const undoFederationRequest = (undoer: User, uri: string): Undo => {
|
export const undoFederationRequest = (undoer: User, uri: string): Undo => {
|
||||||
const id = crypto.randomUUID();
|
const id = crypto.randomUUID();
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,6 @@
|
||||||
import { mentionValidator } from "@/api";
|
import { mentionValidator } from "@/api";
|
||||||
import { sanitizeHtml, sanitizeHtmlInline } from "@/sanitization";
|
import { sanitizeHtml, sanitizeHtmlInline } from "@/sanitization";
|
||||||
import markdownItTaskLists from "@hackmd/markdown-it-task-lists";
|
import markdownItTaskLists from "@hackmd/markdown-it-task-lists";
|
||||||
import { getLogger } from "@logtape/logtape";
|
|
||||||
import {
|
|
||||||
FederationRequester,
|
|
||||||
SignatureConstructor,
|
|
||||||
} from "@lysand-org/federation";
|
|
||||||
import type { ContentFormat } from "@lysand-org/federation/types";
|
import type { ContentFormat } from "@lysand-org/federation/types";
|
||||||
import { config } from "config-manager";
|
import { config } from "config-manager";
|
||||||
import {
|
import {
|
||||||
|
|
@ -37,11 +32,9 @@ import {
|
||||||
type Notes,
|
type Notes,
|
||||||
Users,
|
Users,
|
||||||
} from "~/drizzle/schema";
|
} from "~/drizzle/schema";
|
||||||
import type { Note } from "~/packages/database-interface/note";
|
import type { EmojiWithInstance } from "~/packages/database-interface/emoji";
|
||||||
import { User } from "~/packages/database-interface/user";
|
import { User } from "~/packages/database-interface/user";
|
||||||
import type { Application } from "./application";
|
import type { Application } from "./application";
|
||||||
import type { EmojiWithInstance } from "./emoji";
|
|
||||||
import { objectToInboxRequest } from "./federation";
|
|
||||||
import {
|
import {
|
||||||
type UserWithInstance,
|
type UserWithInstance,
|
||||||
type UserWithRelations,
|
type UserWithRelations,
|
||||||
|
|
@ -316,11 +309,7 @@ export const parseTextMentions = async (
|
||||||
|
|
||||||
// Attempt to resolve mentions that were not found
|
// Attempt to resolve mentions that were not found
|
||||||
for (const person of notFoundRemoteUsers) {
|
for (const person of notFoundRemoteUsers) {
|
||||||
const signatureConstructor = await SignatureConstructor.fromStringKey(
|
const manager = await author.getFederationRequester();
|
||||||
author.data.privateKey ?? "",
|
|
||||||
author.getUri(),
|
|
||||||
);
|
|
||||||
const manager = new FederationRequester(signatureConstructor);
|
|
||||||
|
|
||||||
const uri = await User.webFinger(
|
const uri = await User.webFinger(
|
||||||
manager,
|
manager,
|
||||||
|
|
@ -451,26 +440,3 @@ export const getMarkdownRenderer = () => {
|
||||||
|
|
||||||
return renderer;
|
return renderer;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const federateNote = async (note: Note) => {
|
|
||||||
for (const user of await note.getUsersToFederateTo()) {
|
|
||||||
// TODO: Add queue system
|
|
||||||
const request = await objectToInboxRequest(
|
|
||||||
note.toLysand(),
|
|
||||||
note.author,
|
|
||||||
user,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Send request
|
|
||||||
const response = await fetch(request, {
|
|
||||||
proxy: config.http.proxy.address,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
const logger = getLogger("federation");
|
|
||||||
|
|
||||||
logger.debug`${await response.text()}`;
|
|
||||||
logger.error`Failed to federate status ${note.data.id} to ${user.getUri()}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,3 @@
|
||||||
/**
|
|
||||||
* Old code that needs to be rewritten
|
|
||||||
*/
|
|
||||||
import { getLogger } from "@logtape/logtape";
|
|
||||||
import type {
|
import type {
|
||||||
Follow,
|
Follow,
|
||||||
FollowAccept,
|
FollowAccept,
|
||||||
|
|
@ -19,10 +15,9 @@ import {
|
||||||
Tokens,
|
Tokens,
|
||||||
type Users,
|
type Users,
|
||||||
} from "~/drizzle/schema";
|
} from "~/drizzle/schema";
|
||||||
|
import type { EmojiWithInstance } from "~/packages/database-interface/emoji";
|
||||||
import { User } from "~/packages/database-interface/user";
|
import { User } from "~/packages/database-interface/user";
|
||||||
import type { Application } from "./application";
|
import type { Application } from "./application";
|
||||||
import type { EmojiWithInstance } from "./emoji";
|
|
||||||
import { objectToInboxRequest } from "./federation";
|
|
||||||
import {
|
import {
|
||||||
type Relationship,
|
type Relationship,
|
||||||
checkForBidirectionalRelationships,
|
checkForBidirectionalRelationships,
|
||||||
|
|
@ -168,25 +163,12 @@ export const followRequestUser = async (
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isRemote) {
|
if (isRemote) {
|
||||||
// Federate
|
const { ok } = await follower.federateToUser(
|
||||||
// TODO: Make database job
|
|
||||||
const request = await objectToInboxRequest(
|
|
||||||
followRequestToLysand(follower, followee),
|
followRequestToLysand(follower, followee),
|
||||||
follower,
|
|
||||||
followee,
|
followee,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Send request
|
if (!ok) {
|
||||||
const response = await fetch(request, {
|
|
||||||
proxy: config.http.proxy.address,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
const logger = getLogger("federation");
|
|
||||||
|
|
||||||
logger.debug`${await response.text()}`;
|
|
||||||
logger.error`Failed to federate follow request from ${follower.id} to ${followee.getUri()}`;
|
|
||||||
|
|
||||||
await db
|
await db
|
||||||
.update(Relationships)
|
.update(Relationships)
|
||||||
.set({
|
.set({
|
||||||
|
|
@ -217,45 +199,17 @@ export const followRequestUser = async (
|
||||||
};
|
};
|
||||||
|
|
||||||
export const sendFollowAccept = async (follower: User, followee: User) => {
|
export const sendFollowAccept = async (follower: User, followee: User) => {
|
||||||
// TODO: Make database job
|
await follower.federateToUser(
|
||||||
const request = await objectToInboxRequest(
|
|
||||||
followAcceptToLysand(follower, followee),
|
followAcceptToLysand(follower, followee),
|
||||||
followee,
|
followee,
|
||||||
follower,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Send request
|
|
||||||
const response = await fetch(request, {
|
|
||||||
proxy: config.http.proxy.address,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
const logger = getLogger("federation");
|
|
||||||
|
|
||||||
logger.debug`${await response.text()}`;
|
|
||||||
logger.error`Failed to federate follow accept from ${followee.id} to ${follower.getUri()}`;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const sendFollowReject = async (follower: User, followee: User) => {
|
export const sendFollowReject = async (follower: User, followee: User) => {
|
||||||
// TODO: Make database job
|
await follower.federateToUser(
|
||||||
const request = await objectToInboxRequest(
|
|
||||||
followRejectToLysand(follower, followee),
|
followRejectToLysand(follower, followee),
|
||||||
followee,
|
followee,
|
||||||
follower,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Send request
|
|
||||||
const response = await fetch(request, {
|
|
||||||
proxy: config.http.proxy.address,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
const logger = getLogger("federation");
|
|
||||||
|
|
||||||
logger.debug`${await response.text()}`;
|
|
||||||
logger.error`Failed to federate follow reject from ${followee.id} to ${follower.getUri()}`;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const transformOutputToUserWithRelations = (
|
export const transformOutputToUserWithRelations = (
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,4 @@
|
||||||
import { parseUserAddress, userAddressValidator } from "@/api";
|
import { parseUserAddress, userAddressValidator } from "@/api";
|
||||||
import {
|
|
||||||
FederationRequester,
|
|
||||||
SignatureConstructor,
|
|
||||||
} from "@lysand-org/federation";
|
|
||||||
import { Args } from "@oclif/core";
|
import { Args } from "@oclif/core";
|
||||||
import chalk from "chalk";
|
import chalk from "chalk";
|
||||||
import ora from "ora";
|
import ora from "ora";
|
||||||
|
|
@ -47,11 +43,7 @@ export default class FederationUserFetch extends BaseCommand<
|
||||||
|
|
||||||
const requester = await User.getServerActor();
|
const requester = await User.getServerActor();
|
||||||
|
|
||||||
const signatureConstructor = await SignatureConstructor.fromStringKey(
|
const manager = await requester.getFederationRequester();
|
||||||
requester.data.privateKey ?? "",
|
|
||||||
requester.getUri(),
|
|
||||||
);
|
|
||||||
const manager = new FederationRequester(signatureConstructor);
|
|
||||||
|
|
||||||
const uri = await User.webFinger(manager, username, host);
|
const uri = await User.webFinger(manager, username, host);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,4 @@
|
||||||
import { parseUserAddress, userAddressValidator } from "@/api";
|
import { parseUserAddress, userAddressValidator } from "@/api";
|
||||||
import {
|
|
||||||
FederationRequester,
|
|
||||||
SignatureConstructor,
|
|
||||||
} from "@lysand-org/federation";
|
|
||||||
import { Args } from "@oclif/core";
|
import { Args } from "@oclif/core";
|
||||||
import chalk from "chalk";
|
import chalk from "chalk";
|
||||||
import ora from "ora";
|
import ora from "ora";
|
||||||
|
|
@ -47,11 +43,7 @@ export default class FederationUserFinger extends BaseCommand<
|
||||||
|
|
||||||
const requester = await User.getServerActor();
|
const requester = await User.getServerActor();
|
||||||
|
|
||||||
const signatureConstructor = await SignatureConstructor.fromStringKey(
|
const manager = await requester.getFederationRequester();
|
||||||
requester.data.privateKey ?? "",
|
|
||||||
requester.getUri(),
|
|
||||||
);
|
|
||||||
const manager = new FederationRequester(signatureConstructor);
|
|
||||||
|
|
||||||
const uri = await User.webFinger(manager, username, host);
|
const uri = await User.webFinger(manager, username, host);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,25 @@
|
||||||
|
import { emojiValidatorWithColons } from "@/api";
|
||||||
import { proxyUrl } from "@/response";
|
import { proxyUrl } from "@/response";
|
||||||
import type { Emoji as ApiEmoji } from "@lysand-org/client/types";
|
import type { Emoji as ApiEmoji } from "@lysand-org/client/types";
|
||||||
import type { CustomEmojiExtension } from "@lysand-org/federation/types";
|
import type { CustomEmojiExtension } from "@lysand-org/federation/types";
|
||||||
import {
|
import {
|
||||||
type InferInsertModel,
|
type InferInsertModel,
|
||||||
|
type InferSelectModel,
|
||||||
type SQL,
|
type SQL,
|
||||||
and,
|
and,
|
||||||
desc,
|
desc,
|
||||||
eq,
|
eq,
|
||||||
inArray,
|
inArray,
|
||||||
} from "drizzle-orm";
|
} from "drizzle-orm";
|
||||||
import type { EmojiWithInstance } from "~/classes/functions/emoji";
|
|
||||||
import { db } from "~/drizzle/db";
|
import { db } from "~/drizzle/db";
|
||||||
import { Emojis, Instances } from "~/drizzle/schema";
|
import { Emojis, Instances } from "~/drizzle/schema";
|
||||||
import { BaseInterface } from "./base";
|
import { BaseInterface } from "./base";
|
||||||
import { Instance } from "./instance";
|
import { Instance } from "./instance";
|
||||||
|
|
||||||
|
export type EmojiWithInstance = InferSelectModel<typeof Emojis> & {
|
||||||
|
instance: InferSelectModel<typeof Instances> | null;
|
||||||
|
};
|
||||||
|
|
||||||
export class Emoji extends BaseInterface<typeof Emojis, EmojiWithInstance> {
|
export class Emoji extends BaseInterface<typeof Emojis, EmojiWithInstance> {
|
||||||
async reload(): Promise<void> {
|
async reload(): Promise<void> {
|
||||||
const reloaded = await Emoji.fromId(this.data.id);
|
const reloaded = await Emoji.fromId(this.data.id);
|
||||||
|
|
@ -152,6 +157,26 @@ export class Emoji extends BaseInterface<typeof Emojis, EmojiWithInstance> {
|
||||||
return this.data.id;
|
return this.data.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse emojis from text
|
||||||
|
*
|
||||||
|
* @param text The text to parse
|
||||||
|
* @returns An array of emojis
|
||||||
|
*/
|
||||||
|
public static async parseFromText(text: string): Promise<Emoji[]> {
|
||||||
|
const matches = text.match(emojiValidatorWithColons);
|
||||||
|
if (!matches || matches.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return Emoji.manyFromSql(
|
||||||
|
inArray(
|
||||||
|
Emojis.shortcode,
|
||||||
|
matches.map((match) => match.replace(/:/g, "")),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public toApi(): ApiEmoji {
|
public toApi(): ApiEmoji {
|
||||||
return {
|
return {
|
||||||
// @ts-expect-error ID is not in regular Mastodon API
|
// @ts-expect-error ID is not in regular Mastodon API
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import { getLogger } from "@logtape/logtape";
|
import { getLogger } from "@logtape/logtape";
|
||||||
import {
|
import {
|
||||||
EntityValidator,
|
EntityValidator,
|
||||||
FederationRequester,
|
|
||||||
type ResponseError,
|
type ResponseError,
|
||||||
type ValidationError,
|
type ValidationError,
|
||||||
} from "@lysand-org/federation";
|
} from "@lysand-org/federation";
|
||||||
|
|
@ -20,6 +19,7 @@ import {
|
||||||
import { db } from "~/drizzle/db";
|
import { db } from "~/drizzle/db";
|
||||||
import { Instances } from "~/drizzle/schema";
|
import { Instances } from "~/drizzle/schema";
|
||||||
import { BaseInterface } from "./base";
|
import { BaseInterface } from "./base";
|
||||||
|
import { User } from "./user";
|
||||||
|
|
||||||
export type AttachmentType = InferSelectModel<typeof Instances>;
|
export type AttachmentType = InferSelectModel<typeof Instances>;
|
||||||
|
|
||||||
|
|
@ -139,8 +139,10 @@ export class Instance extends BaseInterface<typeof Instances> {
|
||||||
const wellKnownUrl = new URL("/.well-known/lysand", origin);
|
const wellKnownUrl = new URL("/.well-known/lysand", origin);
|
||||||
const logger = getLogger("federation");
|
const logger = getLogger("federation");
|
||||||
|
|
||||||
|
const requester = await User.getServerActor().getFederationRequester();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { ok, raw, data } = await new FederationRequester()
|
const { ok, raw, data } = await requester
|
||||||
.get(wellKnownUrl, {
|
.get(wellKnownUrl, {
|
||||||
// @ts-expect-error Bun extension
|
// @ts-expect-error Bun extension
|
||||||
proxy: config.http.proxy.address,
|
proxy: config.http.proxy.address,
|
||||||
|
|
@ -193,13 +195,14 @@ export class Instance extends BaseInterface<typeof Instances> {
|
||||||
// Go to endpoint, then follow the links to the actual metadata
|
// Go to endpoint, then follow the links to the actual metadata
|
||||||
|
|
||||||
const logger = getLogger("federation");
|
const logger = getLogger("federation");
|
||||||
|
const requester = await User.getServerActor().getFederationRequester();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
raw: response,
|
raw: response,
|
||||||
ok,
|
ok,
|
||||||
data: wellKnown,
|
data: wellKnown,
|
||||||
} = await new FederationRequester()
|
} = await requester
|
||||||
.get<{
|
.get<{
|
||||||
links: { rel: string; href: string }[];
|
links: { rel: string; href: string }[];
|
||||||
}>(wellKnownUrl, {
|
}>(wellKnownUrl, {
|
||||||
|
|
@ -245,7 +248,7 @@ export class Instance extends BaseInterface<typeof Instances> {
|
||||||
raw: metadataResponse,
|
raw: metadataResponse,
|
||||||
ok: ok2,
|
ok: ok2,
|
||||||
data: metadata,
|
data: metadata,
|
||||||
} = await new FederationRequester()
|
} = await requester
|
||||||
.get<{
|
.get<{
|
||||||
metadata: {
|
metadata: {
|
||||||
nodeName?: string;
|
nodeName?: string;
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import type {
|
||||||
Attachment as ApiAttachment,
|
Attachment as ApiAttachment,
|
||||||
Status as ApiStatus,
|
Status as ApiStatus,
|
||||||
} from "@lysand-org/client/types";
|
} from "@lysand-org/client/types";
|
||||||
import { EntityValidator, FederationRequester } from "@lysand-org/federation";
|
import { EntityValidator } from "@lysand-org/federation";
|
||||||
import type {
|
import type {
|
||||||
ContentFormat,
|
ContentFormat,
|
||||||
Note as LysandNote,
|
Note as LysandNote,
|
||||||
|
|
@ -29,7 +29,6 @@ import {
|
||||||
type Application,
|
type Application,
|
||||||
applicationToApi,
|
applicationToApi,
|
||||||
} from "~/classes/functions/application";
|
} from "~/classes/functions/application";
|
||||||
import { parseEmojis } from "~/classes/functions/emoji";
|
|
||||||
import { localObjectUri } from "~/classes/functions/federation";
|
import { localObjectUri } from "~/classes/functions/federation";
|
||||||
import {
|
import {
|
||||||
type StatusWithRelations,
|
type StatusWithRelations,
|
||||||
|
|
@ -190,6 +189,14 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
||||||
return this.data.id;
|
return this.data.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async federateToUsers(): Promise<void> {
|
||||||
|
const users = await this.getUsersToFederateTo();
|
||||||
|
|
||||||
|
for (const user of users) {
|
||||||
|
await this.author.federateToUser(this.toLysand(), user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch the users that should be federated to for this note
|
* Fetch the users that should be federated to for this note
|
||||||
*
|
*
|
||||||
|
|
@ -336,7 +343,7 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
||||||
|
|
||||||
const parsedEmojis = [
|
const parsedEmojis = [
|
||||||
...(data.emojis ?? []),
|
...(data.emojis ?? []),
|
||||||
...(await parseEmojis(plaintextContent)),
|
...(await Emoji.parseFromText(plaintextContent)),
|
||||||
// Deduplicate by .id
|
// Deduplicate by .id
|
||||||
].filter(
|
].filter(
|
||||||
(emoji, index, self) =>
|
(emoji, index, self) =>
|
||||||
|
|
@ -429,7 +436,9 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
||||||
|
|
||||||
const parsedEmojis = [
|
const parsedEmojis = [
|
||||||
...(data.emojis ?? []),
|
...(data.emojis ?? []),
|
||||||
...(plaintextContent ? await parseEmojis(plaintextContent) : []),
|
...(plaintextContent
|
||||||
|
? await Emoji.parseFromText(plaintextContent)
|
||||||
|
: []),
|
||||||
// Deduplicate by .id
|
// Deduplicate by .id
|
||||||
].filter(
|
].filter(
|
||||||
(emoji, index, self) =>
|
(emoji, index, self) =>
|
||||||
|
|
@ -592,7 +601,10 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
||||||
throw new Error(`Invalid URI to parse ${uri}`);
|
throw new Error(`Invalid URI to parse ${uri}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data } = await new FederationRequester().get(uri, {
|
const requester =
|
||||||
|
await User.getServerActor().getFederationRequester();
|
||||||
|
|
||||||
|
const { data } = await requester.get(uri, {
|
||||||
// @ts-expect-error Bun extension
|
// @ts-expect-error Bun extension
|
||||||
proxy: config.http.proxy.address,
|
proxy: config.http.proxy.address,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,20 @@ import { idValidator } from "@/api";
|
||||||
import { getBestContentType, urlToContentFormat } from "@/content_types";
|
import { getBestContentType, urlToContentFormat } from "@/content_types";
|
||||||
import { randomString } from "@/math";
|
import { randomString } from "@/math";
|
||||||
import { proxyUrl } from "@/response";
|
import { proxyUrl } from "@/response";
|
||||||
|
import { sentry } from "@/sentry";
|
||||||
|
import { getLogger } from "@logtape/logtape";
|
||||||
import type {
|
import type {
|
||||||
Account as ApiAccount,
|
Account as ApiAccount,
|
||||||
Mention as ApiMention,
|
Mention as ApiMention,
|
||||||
} from "@lysand-org/client/types";
|
} from "@lysand-org/client/types";
|
||||||
import { EntityValidator, FederationRequester } from "@lysand-org/federation";
|
import {
|
||||||
|
EntityValidator,
|
||||||
|
FederationRequester,
|
||||||
|
type HttpVerb,
|
||||||
|
SignatureConstructor,
|
||||||
|
} from "@lysand-org/federation";
|
||||||
import type { Entity, User as LysandUser } from "@lysand-org/federation/types";
|
import type { Entity, User as LysandUser } from "@lysand-org/federation/types";
|
||||||
|
import chalk from "chalk";
|
||||||
import {
|
import {
|
||||||
type InferInsertModel,
|
type InferInsertModel,
|
||||||
type SQL,
|
type SQL,
|
||||||
|
|
@ -23,7 +31,6 @@ import {
|
||||||
sql,
|
sql,
|
||||||
} from "drizzle-orm";
|
} from "drizzle-orm";
|
||||||
import { htmlToText } from "html-to-text";
|
import { htmlToText } from "html-to-text";
|
||||||
import { objectToInboxRequest } from "~/classes/functions/federation";
|
|
||||||
import {
|
import {
|
||||||
type UserWithRelations,
|
type UserWithRelations,
|
||||||
findManyUsers,
|
findManyUsers,
|
||||||
|
|
@ -341,9 +348,8 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
||||||
uri: string,
|
uri: string,
|
||||||
instance: Instance,
|
instance: Instance,
|
||||||
): Promise<User> {
|
): Promise<User> {
|
||||||
const { data: json } = await new FederationRequester().get<
|
const requester = await User.getServerActor().getFederationRequester();
|
||||||
Partial<LysandUser>
|
const { data: json } = await requester.get<Partial<LysandUser>>(uri, {
|
||||||
>(uri, {
|
|
||||||
// @ts-expect-error Bun extension
|
// @ts-expect-error Bun extension
|
||||||
proxy: config.http.proxy.address,
|
proxy: config.http.proxy.address,
|
||||||
});
|
});
|
||||||
|
|
@ -617,7 +623,66 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
||||||
return updated.data;
|
return updated.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
async federateToFollowers(object: Entity) {
|
/**
|
||||||
|
* Signs a Lysand entity with that user's private key
|
||||||
|
*
|
||||||
|
* @param entity Entity to sign
|
||||||
|
* @param signatureUrl URL to embed in signature (must be the same URI of queries made with this signature)
|
||||||
|
* @param signatureMethod HTTP method to embed in signature (default: POST)
|
||||||
|
* @returns The signed string and headers to send with the request
|
||||||
|
*/
|
||||||
|
async sign(
|
||||||
|
entity: Entity,
|
||||||
|
signatureUrl: string | URL,
|
||||||
|
signatureMethod: HttpVerb = "POST",
|
||||||
|
): Promise<{
|
||||||
|
headers: Headers;
|
||||||
|
signedString: string;
|
||||||
|
}> {
|
||||||
|
const signatureConstructor = await SignatureConstructor.fromStringKey(
|
||||||
|
this.data.privateKey ?? "",
|
||||||
|
this.getUri(),
|
||||||
|
);
|
||||||
|
|
||||||
|
const output = await signatureConstructor.sign(
|
||||||
|
signatureMethod,
|
||||||
|
new URL(signatureUrl),
|
||||||
|
JSON.stringify(entity),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (config.debug.federation) {
|
||||||
|
const logger = getLogger("federation");
|
||||||
|
|
||||||
|
// Log public key
|
||||||
|
logger.debug`Sender public key: ${this.data.publicKey}`;
|
||||||
|
|
||||||
|
// Log signed string
|
||||||
|
logger.debug`Signed string:\n${output.signedString}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to get the appropriate Lysand SDK requester with this user's private key
|
||||||
|
*
|
||||||
|
* @returns The requester
|
||||||
|
*/
|
||||||
|
async getFederationRequester(): Promise<FederationRequester> {
|
||||||
|
const signatureConstructor = await SignatureConstructor.fromStringKey(
|
||||||
|
this.data.privateKey ?? "",
|
||||||
|
this.getUri(),
|
||||||
|
);
|
||||||
|
|
||||||
|
return new FederationRequester(signatureConstructor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Federates an entity to all followers of the user
|
||||||
|
*
|
||||||
|
* @param entity Entity to federate
|
||||||
|
*/
|
||||||
|
async federateToFollowers(entity: Entity): Promise<void> {
|
||||||
// Get followers
|
// Get followers
|
||||||
const followers = await User.manyFromSql(
|
const followers = await User.manyFromSql(
|
||||||
and(
|
and(
|
||||||
|
|
@ -627,19 +692,45 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
||||||
);
|
);
|
||||||
|
|
||||||
for (const follower of followers) {
|
for (const follower of followers) {
|
||||||
const federationRequest = await objectToInboxRequest(
|
await this.federateToUser(entity, follower);
|
||||||
object,
|
|
||||||
this,
|
|
||||||
follower,
|
|
||||||
);
|
|
||||||
|
|
||||||
// FIXME: Add to new queue system when it's implemented
|
|
||||||
fetch(federationRequest, {
|
|
||||||
proxy: config.http.proxy.address,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Federates an entity to any user.
|
||||||
|
*
|
||||||
|
* @param entity Entity to federate
|
||||||
|
* @param user User to federate to
|
||||||
|
* @returns Whether the federation was successful
|
||||||
|
*/
|
||||||
|
async federateToUser(entity: Entity, user: User): Promise<{ ok: boolean }> {
|
||||||
|
const { headers } = await this.sign(
|
||||||
|
entity,
|
||||||
|
user.data.endpoints?.inbox ?? "",
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await new FederationRequester().post(
|
||||||
|
user.data.endpoints?.inbox ?? "",
|
||||||
|
entity,
|
||||||
|
{
|
||||||
|
// @ts-expect-error Bun extension
|
||||||
|
proxy: config.http.proxy.address,
|
||||||
|
headers,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
getLogger("federation")
|
||||||
|
.error`Federating ${chalk.gray(entity.type)} to ${user.getUri()} ${chalk.bold.red("failed")}`;
|
||||||
|
getLogger("federation").error`${e}`;
|
||||||
|
sentry?.captureException(e);
|
||||||
|
|
||||||
|
return { ok: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ok: true };
|
||||||
|
}
|
||||||
|
|
||||||
toApi(isOwnAccount = false): ApiAccount {
|
toApi(isOwnAccount = false): ApiAccount {
|
||||||
const user = this.data;
|
const user = this.data;
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,6 @@ import { applyConfig, auth, handleZodError } from "@/api";
|
||||||
import { errorResponse, jsonResponse } from "@/response";
|
import { errorResponse, jsonResponse } from "@/response";
|
||||||
import type { Hono } from "@hono/hono";
|
import type { Hono } from "@hono/hono";
|
||||||
import { zValidator } from "@hono/zod-validator";
|
import { zValidator } from "@hono/zod-validator";
|
||||||
import {
|
|
||||||
FederationRequester,
|
|
||||||
SignatureConstructor,
|
|
||||||
} from "@lysand-org/federation";
|
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import {
|
import {
|
||||||
anyOf,
|
anyOf,
|
||||||
|
|
@ -84,12 +80,7 @@ export default (app: Hono) =>
|
||||||
|
|
||||||
const requester = user ?? User.getServerActor();
|
const requester = user ?? User.getServerActor();
|
||||||
|
|
||||||
const signatureConstructor =
|
const manager = await requester.getFederationRequester();
|
||||||
await SignatureConstructor.fromStringKey(
|
|
||||||
requester.data.privateKey ?? "",
|
|
||||||
requester.getUri(),
|
|
||||||
);
|
|
||||||
const manager = new FederationRequester(signatureConstructor);
|
|
||||||
|
|
||||||
const uri = await User.webFinger(manager, username, domain);
|
const uri = await User.webFinger(manager, username, domain);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,6 @@ import { applyConfig, auth, handleZodError } from "@/api";
|
||||||
import { errorResponse, jsonResponse } from "@/response";
|
import { errorResponse, jsonResponse } from "@/response";
|
||||||
import type { Hono } from "@hono/hono";
|
import type { Hono } from "@hono/hono";
|
||||||
import { zValidator } from "@hono/zod-validator";
|
import { zValidator } from "@hono/zod-validator";
|
||||||
import {
|
|
||||||
FederationRequester,
|
|
||||||
SignatureConstructor,
|
|
||||||
} from "@lysand-org/federation";
|
|
||||||
import { eq, ilike, not, or, sql } from "drizzle-orm";
|
import { eq, ilike, not, or, sql } from "drizzle-orm";
|
||||||
import {
|
import {
|
||||||
anyOf,
|
anyOf,
|
||||||
|
|
@ -95,12 +91,7 @@ export default (app: Hono) =>
|
||||||
if (resolve && username && host) {
|
if (resolve && username && host) {
|
||||||
const requester = self ?? User.getServerActor();
|
const requester = self ?? User.getServerActor();
|
||||||
|
|
||||||
const signatureConstructor =
|
const manager = await requester.getFederationRequester();
|
||||||
await SignatureConstructor.fromStringKey(
|
|
||||||
requester.data.privateKey ?? "",
|
|
||||||
requester.getUri(),
|
|
||||||
);
|
|
||||||
const manager = new FederationRequester(signatureConstructor);
|
|
||||||
|
|
||||||
const uri = await User.webFinger(manager, username, host);
|
const uri = await User.webFinger(manager, username, host);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,12 @@ import { config } from "config-manager";
|
||||||
import { and, eq, isNull } from "drizzle-orm";
|
import { and, eq, isNull } from "drizzle-orm";
|
||||||
import ISO6391 from "iso-639-1";
|
import ISO6391 from "iso-639-1";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { parseEmojis } from "~/classes/functions/emoji";
|
|
||||||
import { contentToHtml } from "~/classes/functions/status";
|
import { contentToHtml } from "~/classes/functions/status";
|
||||||
import { MediaManager } from "~/classes/media/media-manager";
|
import { MediaManager } from "~/classes/media/media-manager";
|
||||||
import { db } from "~/drizzle/db";
|
import { db } from "~/drizzle/db";
|
||||||
import { EmojiToUser, RolePermissions, Users } from "~/drizzle/schema";
|
import { EmojiToUser, RolePermissions, Users } from "~/drizzle/schema";
|
||||||
import { Attachment } from "~/packages/database-interface/attachment";
|
import { Attachment } from "~/packages/database-interface/attachment";
|
||||||
import type { Emoji } from "~/packages/database-interface/emoji";
|
import { Emoji } from "~/packages/database-interface/emoji";
|
||||||
import { User } from "~/packages/database-interface/user";
|
import { User } from "~/packages/database-interface/user";
|
||||||
|
|
||||||
export const meta = applyConfig({
|
export const meta = applyConfig({
|
||||||
|
|
@ -252,8 +251,8 @@ export default (app: Hono) =>
|
||||||
);
|
);
|
||||||
|
|
||||||
// Parse emojis
|
// Parse emojis
|
||||||
const nameEmojis = await parseEmojis(parsedName);
|
const nameEmojis = await Emoji.parseFromText(parsedName);
|
||||||
const valueEmojis = await parseEmojis(parsedValue);
|
const valueEmojis = await Emoji.parseFromText(parsedValue);
|
||||||
|
|
||||||
fieldEmojis.push(...nameEmojis, ...valueEmojis);
|
fieldEmojis.push(...nameEmojis, ...valueEmojis);
|
||||||
|
|
||||||
|
|
@ -279,8 +278,9 @@ export default (app: Hono) =>
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse emojis
|
// Parse emojis
|
||||||
const displaynameEmojis = await parseEmojis(sanitizedDisplayName);
|
const displaynameEmojis =
|
||||||
const noteEmojis = await parseEmojis(self.note);
|
await Emoji.parseFromText(sanitizedDisplayName);
|
||||||
|
const noteEmojis = await Emoji.parseFromText(self.note);
|
||||||
|
|
||||||
self.emojis = [
|
self.emojis = [
|
||||||
...displaynameEmojis,
|
...displaynameEmojis,
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import { zValidator } from "@hono/zod-validator";
|
||||||
import { config } from "config-manager";
|
import { config } from "config-manager";
|
||||||
import ISO6391 from "iso-639-1";
|
import ISO6391 from "iso-639-1";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { federateNote } from "~/classes/functions/status";
|
|
||||||
import { RolePermissions } from "~/drizzle/schema";
|
import { RolePermissions } from "~/drizzle/schema";
|
||||||
import { Attachment } from "~/packages/database-interface/attachment";
|
import { Attachment } from "~/packages/database-interface/attachment";
|
||||||
import { Note } from "~/packages/database-interface/note";
|
import { Note } from "~/packages/database-interface/note";
|
||||||
|
|
@ -164,7 +163,7 @@ export default (app: Hono) =>
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!local_only) {
|
if (!local_only) {
|
||||||
await federateNote(newNote);
|
await newNote.federateToUsers();
|
||||||
}
|
}
|
||||||
|
|
||||||
return jsonResponse(await newNote.toApi(user));
|
return jsonResponse(await newNote.toApi(user));
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,6 @@ import {
|
||||||
import { errorResponse, jsonResponse } from "@/response";
|
import { errorResponse, jsonResponse } from "@/response";
|
||||||
import type { Hono } from "@hono/hono";
|
import type { Hono } from "@hono/hono";
|
||||||
import { zValidator } from "@hono/zod-validator";
|
import { zValidator } from "@hono/zod-validator";
|
||||||
import {
|
|
||||||
FederationRequester,
|
|
||||||
SignatureConstructor,
|
|
||||||
} from "@lysand-org/federation";
|
|
||||||
import { and, eq, inArray, sql } from "drizzle-orm";
|
import { and, eq, inArray, sql } from "drizzle-orm";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { searchManager } from "~/classes/search/search-manager";
|
import { searchManager } from "~/classes/search/search-manager";
|
||||||
|
|
@ -133,14 +129,8 @@ export default (app: Hono) =>
|
||||||
if (resolve) {
|
if (resolve) {
|
||||||
const requester = self ?? User.getServerActor();
|
const requester = self ?? User.getServerActor();
|
||||||
|
|
||||||
const signatureConstructor =
|
const manager =
|
||||||
await SignatureConstructor.fromStringKey(
|
await requester.getFederationRequester();
|
||||||
requester.data.privateKey ?? "",
|
|
||||||
requester.getUri(),
|
|
||||||
);
|
|
||||||
const manager = new FederationRequester(
|
|
||||||
signatureConstructor,
|
|
||||||
);
|
|
||||||
|
|
||||||
const uri = await User.webFinger(
|
const uri = await User.webFinger(
|
||||||
manager,
|
manager,
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,6 @@ import { applyConfig, handleZodError } from "@/api";
|
||||||
import { errorResponse, response } from "@/response";
|
import { errorResponse, response } from "@/response";
|
||||||
import type { Hono } from "@hono/hono";
|
import type { Hono } from "@hono/hono";
|
||||||
import { zValidator } from "@hono/zod-validator";
|
import { zValidator } from "@hono/zod-validator";
|
||||||
import { getLogger } from "@logtape/logtape";
|
|
||||||
import { SignatureConstructor } from "@lysand-org/federation";
|
|
||||||
import type { Entity } from "@lysand-org/federation/types";
|
import type { Entity } from "@lysand-org/federation/types";
|
||||||
import { and, eq, inArray, sql } from "drizzle-orm";
|
import { and, eq, inArray, sql } from "drizzle-orm";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
@ -100,22 +98,7 @@ export default (app: Hono) =>
|
||||||
|
|
||||||
const author = foundAuthor ?? User.getServerActor();
|
const author = foundAuthor ?? User.getServerActor();
|
||||||
|
|
||||||
const { headers, signedString } = await (
|
const { headers } = await author.sign(apiObject, reqUrl, "GET");
|
||||||
await SignatureConstructor.fromStringKey(
|
|
||||||
author.data.privateKey ?? "",
|
|
||||||
author.getUri(),
|
|
||||||
)
|
|
||||||
).sign("POST", reqUrl, objectString);
|
|
||||||
|
|
||||||
if (config.debug.federation) {
|
|
||||||
const logger = getLogger("federation");
|
|
||||||
|
|
||||||
// Log public key
|
|
||||||
logger.debug`Sender public key: ${author.data.publicKey}`;
|
|
||||||
|
|
||||||
// Log signed string
|
|
||||||
logger.debug`Signed string:\n${signedString}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return response(objectString, 200, {
|
return response(objectString, 200, {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,6 @@ import { applyConfig, handleZodError } from "@/api";
|
||||||
import { errorResponse, redirect, response } from "@/response";
|
import { errorResponse, redirect, response } from "@/response";
|
||||||
import type { Hono } from "@hono/hono";
|
import type { Hono } from "@hono/hono";
|
||||||
import { zValidator } from "@hono/zod-validator";
|
import { zValidator } from "@hono/zod-validator";
|
||||||
import { getLogger } from "@logtape/logtape";
|
|
||||||
import { SignatureConstructor } from "@lysand-org/federation";
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { config } from "~/packages/config-manager";
|
import { config } from "~/packages/config-manager";
|
||||||
import { User } from "~/packages/database-interface/user";
|
import { User } from "~/packages/database-interface/user";
|
||||||
|
|
@ -84,22 +82,7 @@ export default (app: Hono) =>
|
||||||
reqUrl.protocol = "https:";
|
reqUrl.protocol = "https:";
|
||||||
}
|
}
|
||||||
|
|
||||||
const { headers, signedString } = await (
|
const { headers } = await user.sign(user.toLysand(), reqUrl, "GET");
|
||||||
await SignatureConstructor.fromStringKey(
|
|
||||||
user.data.privateKey ?? "",
|
|
||||||
user.getUri(),
|
|
||||||
)
|
|
||||||
).sign("POST", reqUrl, userString);
|
|
||||||
|
|
||||||
if (config.debug.federation) {
|
|
||||||
const logger = getLogger("federation");
|
|
||||||
|
|
||||||
// Log public key
|
|
||||||
logger.debug`Sender public key: ${user.data.publicKey}`;
|
|
||||||
|
|
||||||
// Log signed string
|
|
||||||
logger.debug`Signed string:\n${signedString}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return response(userString, 200, {
|
return response(userString, 200, {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,7 @@ import { errorResponse, jsonResponse } from "@/response";
|
||||||
import type { Hono } from "@hono/hono";
|
import type { Hono } from "@hono/hono";
|
||||||
import { zValidator } from "@hono/zod-validator";
|
import { zValidator } from "@hono/zod-validator";
|
||||||
import { getLogger } from "@logtape/logtape";
|
import { getLogger } from "@logtape/logtape";
|
||||||
import {
|
import type { ResponseError } from "@lysand-org/federation";
|
||||||
FederationRequester,
|
|
||||||
type ResponseError,
|
|
||||||
SignatureConstructor,
|
|
||||||
} from "@lysand-org/federation";
|
|
||||||
import { and, eq, isNull } from "drizzle-orm";
|
import { and, eq, isNull } from "drizzle-orm";
|
||||||
import { lookup } from "mime-types";
|
import { lookup } from "mime-types";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
@ -84,13 +80,7 @@ export default (app: Hono) =>
|
||||||
if (config.federation.bridge.enabled) {
|
if (config.federation.bridge.enabled) {
|
||||||
const requester = await User.getServerActor();
|
const requester = await User.getServerActor();
|
||||||
|
|
||||||
const signatureConstructor =
|
const manager = await requester.getFederationRequester();
|
||||||
await SignatureConstructor.fromStringKey(
|
|
||||||
requester.data.privateKey ?? "",
|
|
||||||
requester.getUri(),
|
|
||||||
);
|
|
||||||
|
|
||||||
const manager = new FederationRequester(signatureConstructor);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
activityPubUrl = await manager.webFinger(
|
activityPubUrl = await manager.webFinger(
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue