diff --git a/packages/api/routes/versia/v0.6/[entity_type]/[id]/collections/[collection_type].ts b/packages/api/routes/versia/v0.6/[entity_type]/[id]/collections/[collection_type].ts index d4e5a217..9236a65e 100644 --- a/packages/api/routes/versia/v0.6/[entity_type]/[id]/collections/[collection_type].ts +++ b/packages/api/routes/versia/v0.6/[entity_type]/[id]/collections/[collection_type].ts @@ -4,6 +4,7 @@ import { EntitySchema, URICollectionSchema, } from "@versia/sdk/schemas"; +import { config } from "@versia-server/config"; import { ApiError } from "@versia-server/kit"; import { apiRoute, handleZodError } from "@versia-server/kit/api"; import { db, Instance, Note, User } from "@versia-server/kit/db"; @@ -112,13 +113,16 @@ export default apiRoute((app) => ), ); - entity = new VersiaEntities.URICollection({ - author: note.author.id, - total: replyCount, - items: replies.map((reply) => - reply.reference.toString(), - ), - }); + entity = new VersiaEntities.URICollection( + { + author: note.author.id, + total: replyCount, + items: replies.map((reply) => + reply.reference.toString(), + ), + }, + config.http.base_url.hostname, + ); break; } case "quotes": { @@ -146,13 +150,16 @@ export default apiRoute((app) => ), ); - entity = new VersiaEntities.URICollection({ - author: note.author.id, - total: quoteCount, - items: quotes.map((quote) => - quote.reference.toString(), - ), - }); + entity = new VersiaEntities.URICollection( + { + author: note.author.id, + total: quoteCount, + items: quotes.map((quote) => + quote.reference.toString(), + ), + }, + config.http.base_url.hostname, + ); break; } case "pub.versia:share/Shares": { @@ -180,13 +187,16 @@ export default apiRoute((app) => ), ); - entity = new VersiaEntities.URICollection({ - author: note.author.id, - total: shareCount, - items: shares.map((share) => - share.reference.toString(), - ), - }); + entity = new VersiaEntities.URICollection( + { + author: note.author.id, + total: shareCount, + items: shares.map((share) => + share.reference.toString(), + ), + }, + config.http.base_url.hostname, + ); break; } } @@ -226,23 +236,29 @@ export default apiRoute((app) => offset, ); - entity = new VersiaEntities.Collection({ - author: user.id, - total, - items: outboxItems.map((note) => - note.toVersia(), - ), - }); + entity = new VersiaEntities.Collection( + { + author: user.id, + total, + items: outboxItems.map((note) => + note.toVersia(), + ), + }, + config.http.base_url.hostname, + ); break; } case "followers": { if (user.data.isHidingCollections) { - entity = new VersiaEntities.URICollection({ - author: user.id, - items: [], - total: 0, - }); + entity = new VersiaEntities.URICollection( + { + author: user.id, + items: [], + total: 0, + }, + config.http.base_url.hostname, + ); break; } @@ -258,23 +274,29 @@ export default apiRoute((app) => offset, ); - entity = new VersiaEntities.URICollection({ - author: user.id, - items: followers.map((follower) => - follower.reference.toString(), - ), - total, - }); + entity = new VersiaEntities.URICollection( + { + author: user.id, + items: followers.map((follower) => + follower.reference.toString(), + ), + total, + }, + config.http.base_url.hostname, + ); break; } case "following": { if (user.data.isHidingCollections) { - entity = new VersiaEntities.URICollection({ - author: user.id, - items: [], - total: 0, - }); + entity = new VersiaEntities.URICollection( + { + author: user.id, + items: [], + total: 0, + }, + config.http.base_url.hostname, + ); break; } @@ -290,13 +312,16 @@ export default apiRoute((app) => offset, ); - entity = new VersiaEntities.URICollection({ - author: user.id, - items: following.map((followed) => - followed.reference.toString(), - ), - total, - }); + entity = new VersiaEntities.URICollection( + { + author: user.id, + items: following.map((followed) => + followed.reference.toString(), + ), + total, + }, + config.http.base_url.hostname, + ); break; } diff --git a/packages/api/routes/versia/v0.6/inbox.test.ts b/packages/api/routes/versia/v0.6/inbox.test.ts index bcf4d992..af4f475f 100644 --- a/packages/api/routes/versia/v0.6/inbox.test.ts +++ b/packages/api/routes/versia/v0.6/inbox.test.ts @@ -83,15 +83,18 @@ mock(new URL(`/.versia/v0.6/entities/User/${userId}`, instanceUrl).href, { headers: { "Content-Type": "application/vnd.versia+json; charset=utf-8", }, - data: new VersiaEntities.User({ - id: userId, - created_at: "2025-04-18T10:32:01.427Z", - type: "User", - username: "testuser", - fields: [], - manually_approves_followers: false, - indexable: true, - }).toJSON(), + data: new VersiaEntities.User( + { + id: userId, + created_at: "2025-04-18T10:32:01.427Z", + type: "User", + username: "testuser", + fields: [], + manually_approves_followers: false, + indexable: true, + }, + instanceUrl.hostname, + ).toJSON(), }, }); @@ -111,35 +114,38 @@ afterAll(async () => { describe("Inbox Tests", () => { test("should correctly process inbox request", async () => { - const exampleNote = new VersiaEntities.Note({ - id: noteId, - created_at: "2025-04-18T10:32:01.427Z", - type: "Note", - extensions: { - "pub.versia:custom_emojis": { - emojis: [], + const exampleNote = new VersiaEntities.Note( + { + id: noteId, + created_at: "2025-04-18T10:32:01.427Z", + type: "Note", + extensions: { + "pub.versia:custom_emojis": { + emojis: [], + }, }, + previews: [], + attachments: [], + author: userId, + content: { + "text/html": { + content: "

Hello!

", + remote: false, + }, + "text/plain": { + content: "Hello!", + remote: false, + }, + }, + group: "public", + is_sensitive: false, + mentions: [], + quotes: null, + replies_to: null, + subject: "", }, - previews: [], - attachments: [], - author: userId, - content: { - "text/html": { - content: "

Hello!

", - remote: false, - }, - "text/plain": { - content: "Hello!", - remote: false, - }, - }, - group: "public", - is_sensitive: false, - mentions: [], - quotes: null, - replies_to: null, - subject: "", - }); + instanceUrl.hostname, + ); const signedRequest = await sign( instanceKeys.privateKey, @@ -177,13 +183,16 @@ describe("Inbox Tests", () => { }); test("should correctly process Share", async () => { - const exampleRequest = new VersiaEntities.Share({ - id: shareId, - created_at: "2025-04-18T10:32:01.427Z", - type: "pub.versia:share/Share", - author: userId, - shared: noteId, - }); + const exampleRequest = new VersiaEntities.Share( + { + id: shareId, + created_at: "2025-04-18T10:32:01.427Z", + type: "pub.versia:share/Share", + author: userId, + shared: noteId, + }, + instanceUrl.hostname, + ); const signedRequest = await sign( instanceKeys.privateKey, @@ -229,14 +238,17 @@ describe("Inbox Tests", () => { }); test("should correctly process Reaction", async () => { - const exampleRequest = new VersiaEntities.Reaction({ - id: reactionId, - created_at: "2025-04-18T10:32:01.427Z", - type: "pub.versia:reactions/Reaction", - author: userId, - object: noteId, - content: "👍", - }); + const exampleRequest = new VersiaEntities.Reaction( + { + id: reactionId, + created_at: "2025-04-18T10:32:01.427Z", + type: "pub.versia:reactions/Reaction", + author: userId, + object: noteId, + content: "👍", + }, + instanceUrl.hostname, + ); const signedRequest = await sign( instanceKeys.privateKey, @@ -304,34 +316,37 @@ describe("Inbox Tests", () => { }); test("should correctly process Reaction with custom emoji", async () => { - const exampleRequest = new VersiaEntities.Reaction({ - id: reaction2Id, - created_at: "2025-04-18T10:32:01.427Z", - type: "pub.versia:reactions/Reaction", - author: userId, - object: noteId, - content: ":neocat:", - extensions: { - "pub.versia:custom_emojis": { - emojis: [ - { - name: ":neocat:", - url: { - "image/webp": { - hash: "e06240155d2cb90e8dc05327d023585ab9d47216ff547ad72aaf75c485fe9649", - size: 4664, - width: 256, - height: 256, - remote: true, - content: - "https://cdn.cpluspatch.com/versia-cpp/e06240155d2cb90e8dc05327d023585ab9d47216ff547ad72aaf75c485fe9649/neocat.webp", + const exampleRequest = new VersiaEntities.Reaction( + { + id: reaction2Id, + created_at: "2025-04-18T10:32:01.427Z", + type: "pub.versia:reactions/Reaction", + author: userId, + object: noteId, + content: ":neocat:", + extensions: { + "pub.versia:custom_emojis": { + emojis: [ + { + name: ":neocat:", + url: { + "image/webp": { + hash: "e06240155d2cb90e8dc05327d023585ab9d47216ff547ad72aaf75c485fe9649", + size: 4664, + width: 256, + height: 256, + remote: true, + content: + "https://cdn.cpluspatch.com/versia-cpp/e06240155d2cb90e8dc05327d023585ab9d47216ff547ad72aaf75c485fe9649/neocat.webp", + }, }, }, - }, - ], + ], + }, }, }, - }); + instanceUrl.hostname, + ); const signedRequest = await sign( instanceKeys.privateKey, @@ -405,13 +420,16 @@ describe("Inbox Tests", () => { expect(noteToDelete).not.toBeNull(); // Create a Delete request - const exampleRequest = new VersiaEntities.Delete({ - created_at: new Date().toISOString(), - type: "Delete", - author: userId, - deleted_type: "Note", - deleted: noteId, - }); + const exampleRequest = new VersiaEntities.Delete( + { + created_at: new Date().toISOString(), + type: "Delete", + author: userId, + deleted_type: "Note", + deleted: noteId, + }, + instanceUrl.hostname, + ); const signedRequest = await sign( instanceKeys.privateKey, diff --git a/packages/kit/db/like.ts b/packages/kit/db/like.ts index 9b8072c2..9cbcddbc 100644 --- a/packages/kit/db/like.ts +++ b/packages/kit/db/like.ts @@ -1,4 +1,5 @@ import * as VersiaEntities from "@versia/sdk/entities"; +import { config } from "@versia-server/config"; import { and, desc, @@ -20,7 +21,9 @@ import { BaseInterface } from "./base.ts"; import type { User } from "./user.ts"; type LikeType = InferSelectModel & { - liker: InferSelectModel; + liker: InferSelectModel & { + instance: InferSelectModel | null; + }; liked: InferSelectModel & { author: InferSelectModel & { instance: InferSelectModel | null; @@ -70,7 +73,11 @@ export class Like extends BaseInterface { }, }, }, - liker: true, + liker: { + with: { + instance: true, + }, + }, }, }); @@ -102,7 +109,11 @@ export class Like extends BaseInterface { }, }, }, - liker: true, + liker: { + with: { + instance: true, + }, + }, }, }); @@ -167,22 +178,28 @@ export class Like extends BaseInterface { likedReference = `${this.data.liked.author.instance.domain}:${this.data.liked.remoteId}`; } - return new VersiaEntities.Like({ - id: this.id, - author: this.data.liker.id, - type: "pub.versia:likes/Like", - created_at: this.data.createdAt.toISOString(), - liked: likedReference, - }); + return new VersiaEntities.Like( + { + id: this.id, + author: this.data.liker.id, + type: "pub.versia:likes/Like", + created_at: this.data.createdAt.toISOString(), + liked: likedReference, + }, + this.data.liker.instance?.domain ?? config.http.base_url.hostname, + ); } public unlikeToVersia(unliker?: User): VersiaEntities.Delete { - return new VersiaEntities.Delete({ - type: "Delete", - created_at: new Date().toISOString(), - author: unliker ? unliker.id : this.data.liker.id, - deleted_type: "pub.versia:likes/Like", - deleted: this.id, - }); + return new VersiaEntities.Delete( + { + type: "Delete", + created_at: new Date().toISOString(), + author: unliker ? unliker.id : this.data.liker.id, + deleted_type: "pub.versia:likes/Like", + deleted: this.id, + }, + this.data.liker.instance?.domain ?? config.http.base_url.hostname, + ); } } diff --git a/packages/kit/db/note.ts b/packages/kit/db/note.ts index 24bc4932..8d84007f 100644 --- a/packages/kit/db/note.ts +++ b/packages/kit/db/note.ts @@ -426,13 +426,13 @@ export class Note extends BaseInterface { public get reference(): VersiaEntities.Reference { if (this.remote) { - const instanceUrl = new URL( + return new VersiaEntities.Reference( + this.id, this.author.data.instance?.domain || "", ); - return new VersiaEntities.Reference(this.id, instanceUrl.hostname); } - return new VersiaEntities.Reference(this.id); + return new VersiaEntities.Reference(this.id, config.http.base_url.host); } public async federateToUsers(): Promise { @@ -941,19 +941,13 @@ export class Note extends BaseInterface { */ public static async resolve( reference: VersiaEntities.Reference, - defaultInstance?: Instance, ): Promise { // Check if note not already in database - if ( - !(reference.domain || defaultInstance) || - reference.domain === config.http.base_url.hostname - ) { + if (reference.domain === config.http.base_url.hostname) { return await Note.fromId(reference.id); } - const instance = reference.domain - ? await Instance.resolve(reference.domain) - : (defaultInstance as Instance); + const instance = await Instance.resolve(reference.domain); const foundNote = await Note.fromSql( and( @@ -973,14 +967,7 @@ export class Note extends BaseInterface { return foundNote; } - return Note.fromVersia( - reference.domain - ? reference - : new VersiaEntities.Reference( - reference.id, - instance.data.domain, - ), - ); + return Note.fromVersia(reference); } /** @@ -1003,12 +990,6 @@ export class Note extends BaseInterface { instance?: Instance, ): Promise { if (versiaNote instanceof VersiaEntities.Reference) { - if (!versiaNote.domain) { - throw new Error( - "Cannot fetch Versia note from reference without domain", - ); - } - // No bridge support for notes yet const note = await Instance.federationRequester.fetchEntity( versiaNote, @@ -1027,7 +1008,7 @@ export class Note extends BaseInterface { const { created_at, extensions, group, id, is_sensitive, subject } = versiaNote.data; - const author = await User.resolve(versiaNote.author, instance); + const author = await User.resolve(versiaNote.author); if (!author) { throw new Error("Entity author could not be resolved"); @@ -1064,7 +1045,7 @@ export class Note extends BaseInterface { const mentions = ( await Promise.all( - versiaNote.mentions.map((m) => User.resolve(m, instance)) ?? [], + versiaNote.mentions.map((m) => User.resolve(m)) ?? [], ) ).filter((m) => m !== null); @@ -1075,10 +1056,10 @@ export class Note extends BaseInterface { : (group as "public" | "followers" | "unlisted"); const reply = versiaNote.repliesTo - ? await Note.resolve(versiaNote.repliesTo, instance) + ? await Note.resolve(versiaNote.repliesTo) : null; const quote = versiaNote.quotes - ? await Note.resolve(versiaNote.quotes, instance) + ? await Note.resolve(versiaNote.quotes) : null; const spoiler = subject ? await sanitizedHtmlStrip(subject) : undefined; @@ -1296,13 +1277,16 @@ export class Note extends BaseInterface { } public deleteToVersia(): VersiaEntities.Delete { - return new VersiaEntities.Delete({ - type: "Delete", - author: this.author.id, - deleted_type: "Note", - deleted: this.id, - created_at: new Date().toISOString(), - }); + return new VersiaEntities.Delete( + { + type: "Delete", + author: this.author.id, + deleted_type: "Note", + deleted: this.id, + created_at: new Date().toISOString(), + }, + this.data.author.instance?.domain ?? config.http.base_url.hostname, + ); } /** @@ -1324,48 +1308,51 @@ export class Note extends BaseInterface { replyReference = `${status.reply.author.instance.domain}:${status.reply.remoteId}`; } - return new VersiaEntities.Note({ - type: "Note", - created_at: status.createdAt.toISOString(), - id: status.id, - author: this.author.id, - content: { - "text/html": { - content: status.content, - remote: false, + return new VersiaEntities.Note( + { + type: "Note", + created_at: status.createdAt.toISOString(), + id: status.id, + author: this.author.id, + content: { + "text/html": { + content: status.content, + remote: false, + }, + "text/plain": { + content: htmlToText(status.content), + remote: false, + }, }, - "text/plain": { - content: htmlToText(status.content), - remote: false, + previews: [], + attachments: status.attachments.map( + (attachment) => + new Media(attachment).toVersia().data as z.infer< + typeof NonTextContentFormatSchema + >, + ), + is_sensitive: status.sensitive, + mentions: status.mentions.map((mention) => + mention.instance + ? `${mention.instance.domain}:${mention.id}` + : mention.id, + ), + quotes: quoteReference, + replies_to: replyReference, + subject: status.spoilerText, + // TODO: Refactor as part of groups + group: status.visibility === "public" ? "public" : "followers", + extensions: { + "pub.versia:custom_emojis": { + emojis: status.emojis.map((emoji) => + new Emoji(emoji).toVersia(), + ), + }, + // TODO: Add polls and reactions }, }, - previews: [], - attachments: status.attachments.map( - (attachment) => - new Media(attachment).toVersia().data as z.infer< - typeof NonTextContentFormatSchema - >, - ), - is_sensitive: status.sensitive, - mentions: status.mentions.map((mention) => - mention.instance - ? `${mention.instance.domain}:${mention.id}` - : mention.id, - ), - quotes: quoteReference, - replies_to: replyReference, - subject: status.spoilerText, - // TODO: Refactor as part of groups - group: status.visibility === "public" ? "public" : "followers", - extensions: { - "pub.versia:custom_emojis": { - emojis: status.emojis.map((emoji) => - new Emoji(emoji).toVersia(), - ), - }, - // TODO: Add polls and reactions - }, - }); + status.author.instance?.domain ?? config.http.base_url.hostname, + ); } public toVersiaShare(): VersiaEntities.Share { @@ -1373,25 +1360,31 @@ export class Note extends BaseInterface { throw new Error("Cannot share a non-reblogged note"); } - return new VersiaEntities.Share({ - type: "pub.versia:share/Share", - author: this.author.id, - id: this.id, - created_at: new Date().toISOString(), - shared: this.data.reblog.author.instance - ? `${this.data.reblog.author.instance.domain}:${this.data.reblog.id}` - : this.data.reblog.id, - }); + return new VersiaEntities.Share( + { + type: "pub.versia:share/Share", + author: this.author.id, + id: this.id, + created_at: new Date().toISOString(), + shared: this.data.reblog.author.instance + ? `${this.data.reblog.author.instance.domain}:${this.data.reblog.id}` + : this.data.reblog.id, + }, + this.data.author.instance?.domain ?? config.http.base_url.hostname, + ); } public toVersiaUnshare(): VersiaEntities.Delete { - return new VersiaEntities.Delete({ - type: "Delete", - created_at: new Date().toISOString(), - author: this.author.id, - deleted_type: "pub.versia:share/Share", - deleted: this.id, - }); + return new VersiaEntities.Delete( + { + type: "Delete", + created_at: new Date().toISOString(), + author: this.author.id, + deleted_type: "pub.versia:share/Share", + deleted: this.id, + }, + this.data.author.instance?.domain ?? config.http.base_url.hostname, + ); } /** diff --git a/packages/kit/db/reaction.ts b/packages/kit/db/reaction.ts index 88310b4f..8ede1b4e 100644 --- a/packages/kit/db/reaction.ts +++ b/packages/kit/db/reaction.ts @@ -26,7 +26,9 @@ import type { User } from "./user.ts"; type ReactionType = InferSelectModel & { emoji: typeof Emoji.$type | null; - author: InferSelectModel; + author: InferSelectModel & { + instance: InferSelectModel | null; + }; note: InferSelectModel & { author: InferSelectModel & { instance: InferSelectModel | null; @@ -72,7 +74,11 @@ export class Reaction extends BaseInterface { media: true, }, }, - author: true, + author: { + with: { + instance: true, + }, + }, note: { with: { author: { @@ -112,7 +118,11 @@ export class Reaction extends BaseInterface { media: true, }, }, - author: true, + author: { + with: { + instance: true, + }, + }, note: { with: { author: { @@ -245,37 +255,43 @@ export class Reaction extends BaseInterface { noteReference = `${this.data.note.author.instance.domain}:${this.data.note.remoteId}`; } - return new VersiaEntities.Reaction({ - type: "pub.versia:reactions/Reaction", - author: this.data.author.id, - created_at: this.data.createdAt.toISOString(), - id: this.id, - object: noteReference, - content: this.hasCustomEmoji() - ? `:${this.data.emoji?.shortcode}:` - : this.data.emojiText || "", - extensions: this.hasCustomEmoji() - ? { - "pub.versia:custom_emojis": { - emojis: [ - new Emoji( - this.data.emoji as typeof Emoji.$type, - ).toVersia(), - ], - }, - } - : undefined, - }); + return new VersiaEntities.Reaction( + { + type: "pub.versia:reactions/Reaction", + author: this.data.author.id, + created_at: this.data.createdAt.toISOString(), + id: this.id, + object: noteReference, + content: this.hasCustomEmoji() + ? `:${this.data.emoji?.shortcode}:` + : this.data.emojiText || "", + extensions: this.hasCustomEmoji() + ? { + "pub.versia:custom_emojis": { + emojis: [ + new Emoji( + this.data.emoji as typeof Emoji.$type, + ).toVersia(), + ], + }, + } + : undefined, + }, + this.data.author.instance?.domain ?? config.http.base_url.hostname, + ); } public toVersiaUnreact(): VersiaEntities.Delete { - return new VersiaEntities.Delete({ - type: "Delete", - created_at: new Date().toISOString(), - author: this.data.authorId, - deleted_type: "pub.versia:reactions/Reaction", - deleted: this.id, - }); + return new VersiaEntities.Delete( + { + type: "Delete", + created_at: new Date().toISOString(), + author: this.data.authorId, + deleted_type: "pub.versia:reactions/Reaction", + deleted: this.id, + }, + this.data.author.instance?.domain ?? config.http.base_url.hostname, + ); } public static async fromVersia( diff --git a/packages/kit/db/user.ts b/packages/kit/db/user.ts index 1d97e42c..9ab413da 100644 --- a/packages/kit/db/user.ts +++ b/packages/kit/db/user.ts @@ -224,7 +224,10 @@ export class User extends BaseInterface { public get reference(): VersiaEntities.Reference { if (this.local) { - return new VersiaEntities.Reference(this.id); + return new VersiaEntities.Reference( + this.id, + config.http.base_url.hostname, + ); } return new VersiaEntities.Reference( @@ -330,14 +333,17 @@ export class User extends BaseInterface { } private unfollowToVersia(followee: User): VersiaEntities.Unfollow { - return new VersiaEntities.Unfollow({ - type: "Unfollow", - author: this.id, - created_at: new Date().toISOString(), - followee: followee.data.instance - ? `${followee.data.instance.domain}:${followee.id}` - : followee.id, - }); + return new VersiaEntities.Unfollow( + { + type: "Unfollow", + author: this.id, + created_at: new Date().toISOString(), + followee: followee.data.instance + ? `${followee.data.instance.domain}:${followee.id}` + : followee.id, + }, + this.data.instance?.domain ?? config.http.base_url.hostname, + ); } public async acceptFollowRequest(follower: User): Promise { @@ -352,14 +358,17 @@ export class User extends BaseInterface { await follower.recalculateFollowerCount(); await this.recalculateFollowingCount(); - const entity = new VersiaEntities.FollowAccept({ - type: "FollowAccept", - author: this.id, - created_at: new Date().toISOString(), - follower: follower.data.instance - ? `${follower.data.instance.domain}:${follower.id}` - : follower.id, - }); + const entity = new VersiaEntities.FollowAccept( + { + type: "FollowAccept", + author: this.id, + created_at: new Date().toISOString(), + follower: follower.data.instance + ? `${follower.data.instance.domain}:${follower.id}` + : follower.id, + }, + this.data.instance?.domain ?? config.http.base_url.hostname, + ); await deliveryQueue.add(DeliveryJobType.FederateEntity, { entity: entity.toJSON(), @@ -377,14 +386,17 @@ export class User extends BaseInterface { throw new Error("Followee must be a local user"); } - const entity = new VersiaEntities.FollowReject({ - type: "FollowReject", - author: this.id, - created_at: new Date().toISOString(), - follower: follower.data.instance - ? `${follower.data.instance.domain}:${follower.id}` - : follower.id, - }); + const entity = new VersiaEntities.FollowReject( + { + type: "FollowReject", + author: this.id, + created_at: new Date().toISOString(), + follower: follower.data.instance + ? `${follower.data.instance.domain}:${follower.id}` + : follower.id, + }, + this.data.instance?.domain ?? config.http.base_url.hostname, + ); await deliveryQueue.add(DeliveryJobType.FederateEntity, { entity: entity.toJSON(), @@ -686,12 +698,6 @@ export class User extends BaseInterface { instance?: Instance, ): Promise { if (versiaUser instanceof VersiaEntities.Reference) { - if (!versiaUser.domain) { - throw new Error( - "Cannot fetch Versia user from reference without domain", - ); - } - const user = await Instance.federationRequester.fetchEntity( versiaUser, VersiaEntities.User, @@ -802,13 +808,9 @@ export class User extends BaseInterface { public static async resolve( reference: VersiaEntities.Reference, - defaultInstance?: Instance, ): Promise { // Check if user not already in database - if ( - !(reference.domain || defaultInstance) || - reference.domain === config.http.base_url.hostname - ) { + if (reference.domain === config.http.base_url.hostname) { const user = await User.fromId(reference.id); if (!user) { @@ -820,9 +822,7 @@ export class User extends BaseInterface { return user; } - const instance = reference.domain - ? await Instance.resolve(reference.domain) - : (defaultInstance as Instance); + const instance = await Instance.resolve(reference.domain); const foundUser = await User.fromSql( and( @@ -836,12 +836,7 @@ export class User extends BaseInterface { } return User.fromVersia( - reference.domain - ? reference - : new VersiaEntities.Reference( - reference.id, - instance.data.domain, - ), + new VersiaEntities.Reference(reference.id, instance.data.domain), ); } @@ -1088,39 +1083,42 @@ export class User extends BaseInterface { const user = this.data; - return new VersiaEntities.User({ - id: user.id, - type: "User", - bio: { - "text/html": { - content: user.note, - remote: false, + return new VersiaEntities.User( + { + id: user.id, + type: "User", + bio: { + "text/html": { + content: user.note, + remote: false, + }, + "text/plain": { + content: htmlToText(user.note), + remote: false, + }, }, - "text/plain": { - content: htmlToText(user.note), - remote: false, + created_at: user.createdAt.toISOString(), + indexable: this.data.isIndexable, + username: user.username, + manually_approves_followers: this.data.isLocked, + avatar: this.avatar?.toVersia().data as z.infer< + typeof ImageContentFormatSchema + >, + header: this.header?.toVersia().data as z.infer< + typeof ImageContentFormatSchema + >, + display_name: user.displayName, + fields: user.fields, + extensions: { + "pub.versia:custom_emojis": { + emojis: user.emojis.map((emoji) => + new Emoji(emoji).toVersia(), + ), + }, }, }, - created_at: user.createdAt.toISOString(), - indexable: this.data.isIndexable, - username: user.username, - manually_approves_followers: this.data.isLocked, - avatar: this.avatar?.toVersia().data as z.infer< - typeof ImageContentFormatSchema - >, - header: this.header?.toVersia().data as z.infer< - typeof ImageContentFormatSchema - >, - display_name: user.displayName, - fields: user.fields, - extensions: { - "pub.versia:custom_emojis": { - emojis: user.emojis.map((emoji) => - new Emoji(emoji).toVersia(), - ), - }, - }, - }); + this.data.instance?.domain ?? config.http.base_url.hostname, + ); } public toMention(): z.infer { diff --git a/packages/kit/inbox-processor.ts b/packages/kit/inbox-processor.ts index 37a24f33..9e097c2c 100644 --- a/packages/kit/inbox-processor.ts +++ b/packages/kit/inbox-processor.ts @@ -185,33 +185,31 @@ export class InboxProcessor { // TODO: Rip out bridge code so this is never null const instance = this.sender?.instance as Instance; - await new EntitySorter(this.body) + await new EntitySorter(this.body, instance.data.domain) .on(VersiaEntities.Note, (n) => InboxProcessor.processNote(n, instance), ) .on(VersiaEntities.Follow, (f) => - InboxProcessor.processFollowRequest(f, instance), + InboxProcessor.processFollowRequest(f), ) .on(VersiaEntities.FollowAccept, (f) => - InboxProcessor.processFollowAccept(f, instance), + InboxProcessor.processFollowAccept(f), ) .on(VersiaEntities.FollowReject, (f) => - InboxProcessor.processFollowReject(f, instance), + InboxProcessor.processFollowReject(f), ) .on(VersiaEntities.Like, (l) => - InboxProcessor.processLikeRequest(l, instance), + InboxProcessor.processLikeRequest(l), ) .on(VersiaEntities.Delete, (d) => - InboxProcessor.processDelete(d, instance), + InboxProcessor.processDelete(d), ) .on(VersiaEntities.User, (u) => InboxProcessor.processUser(u, instance), ) - .on(VersiaEntities.Share, (s) => - InboxProcessor.processShare(s, instance), - ) + .on(VersiaEntities.Share, (s) => InboxProcessor.processShare(s)) .on(VersiaEntities.Reaction, (r) => - InboxProcessor.processReaction(r, instance), + InboxProcessor.processReaction(r), ) .sort(() => { throw new ApiError(400, "Unknown entity type"); @@ -229,10 +227,9 @@ export class InboxProcessor { */ private static async processReaction( reaction: VersiaEntities.Reaction, - sender: Instance, ): Promise { - const author = await User.resolve(reaction.author, sender); - const note = await Note.resolve(reaction.object, sender); + const author = await User.resolve(reaction.author); + const note = await Note.resolve(reaction.object); if (!author) { throw new ApiError(404, "Author not found"); @@ -322,10 +319,9 @@ export class InboxProcessor { */ private static async processFollowRequest( follow: VersiaEntities.Follow, - sender: Instance, ): Promise { - const author = await User.resolve(follow.author, sender); - const followee = await User.resolve(follow.followee, sender); + const author = await User.resolve(follow.author); + const followee = await User.resolve(follow.followee); if (!author) { throw new ApiError(404, "Author not found"); @@ -371,10 +367,9 @@ export class InboxProcessor { */ private static async processFollowAccept( followAccept: VersiaEntities.FollowAccept, - sender: Instance, ): Promise { - const author = await User.resolve(followAccept.author, sender); - const follower = await User.resolve(followAccept.follower, sender); + const author = await User.resolve(followAccept.author); + const follower = await User.resolve(followAccept.follower); if (!author) { throw new ApiError(404, "Author not found"); @@ -407,10 +402,9 @@ export class InboxProcessor { */ private static async processFollowReject( followReject: VersiaEntities.FollowReject, - sender: Instance, ): Promise { - const author = await User.resolve(followReject.author, sender); - const follower = await User.resolve(followReject.follower, sender); + const author = await User.resolve(followReject.author); + const follower = await User.resolve(followReject.follower); if (!author) { throw new ApiError(404, "Author not found"); @@ -443,10 +437,9 @@ export class InboxProcessor { */ private static async processShare( share: VersiaEntities.Share, - sender: Instance, ): Promise { - const author = await User.resolve(share.author, sender); - const sharedNote = await Note.resolve(share.shared, sender); + const author = await User.resolve(share.author); + const sharedNote = await Note.resolve(share.shared); if (!author) { throw new ApiError(404, "Author not found"); @@ -467,11 +460,10 @@ export class InboxProcessor { */ // JS doesn't allow the use of `delete` as a variable name public static async processDelete( delete_: VersiaEntities.Delete, - sender: Instance, ): Promise { const toDelete = delete_.deleted; - const author = await User.resolve(delete_.author, sender); + const author = await User.resolve(delete_.author); switch (delete_.data.deleted_type) { case "Note": { @@ -491,7 +483,7 @@ export class InboxProcessor { return; } case "User": { - const userToDelete = await User.resolve(toDelete, sender); + const userToDelete = await User.resolve(toDelete); if (!userToDelete) { throw new ApiError(404, "User to delete not found"); @@ -586,10 +578,9 @@ export class InboxProcessor { */ private static async processLikeRequest( like: VersiaEntities.Like, - sender: Instance, ): Promise { - const author = await User.resolve(like.author, sender); - const likedNote = await Note.resolve(like.liked, sender); + const author = await User.resolve(like.author); + const likedNote = await Note.resolve(like.liked); if (!author) { throw new ApiError(404, "Author not found"); diff --git a/packages/kit/queues/delivery/worker.ts b/packages/kit/queues/delivery/worker.ts index b56d475d..964eed29 100644 --- a/packages/kit/queues/delivery/worker.ts +++ b/packages/kit/queues/delivery/worker.ts @@ -62,7 +62,10 @@ export const getDeliveryWorker = (): Worker< } await sender.federateToUser( - await entityCtor.fromJSON(entity), + await entityCtor.fromJSON( + entity, + config.http.base_url.hostname, + ), recipient, ); diff --git a/packages/sdk/entities/collection.ts b/packages/sdk/entities/collection.ts index a1093b29..a3d79b60 100644 --- a/packages/sdk/entities/collection.ts +++ b/packages/sdk/entities/collection.ts @@ -7,25 +7,37 @@ import type { JSONObject } from "../types.ts"; import { Entity } from "./entity.ts"; export class Collection extends Entity { - public constructor(public override data: z.infer) { - super(data); + public constructor( + public override data: z.infer, + instanceDomain: string, + ) { + super(data, instanceDomain); } - public static override fromJSON(json: JSONObject): Promise { - return CollectionSchema.parseAsync(json).then((u) => new Collection(u)); + public static override fromJSON( + json: JSONObject, + instanceDomain: string, + ): Promise { + return CollectionSchema.parseAsync(json).then( + (u) => new Collection(u, instanceDomain), + ); } } export class URICollection extends Entity { public constructor( public override data: z.infer, + instanceDomain: string, ) { - super(data); + super(data, instanceDomain); } - public static override fromJSON(json: JSONObject): Promise { + public static override fromJSON( + json: JSONObject, + instanceDomain: string, + ): Promise { return URICollectionSchema.parseAsync(json).then( - (u) => new URICollection(u), + (u) => new URICollection(u, instanceDomain), ); } } diff --git a/packages/sdk/entities/delete.ts b/packages/sdk/entities/delete.ts index 7a16b5fa..3d572cf0 100644 --- a/packages/sdk/entities/delete.ts +++ b/packages/sdk/entities/delete.ts @@ -6,19 +6,27 @@ import { Entity, Reference } from "./entity.ts"; export class Delete extends Entity { public static override name = "Delete"; - public constructor(public override data: z.infer) { - super(data); + public constructor( + public override data: z.infer, + instanceDomain: string, + ) { + super(data, instanceDomain); } public get author(): Reference { - return Reference.fromString(this.data.author); + return Reference.fromString(this.data.author, this.instanceDomain); } public get deleted(): Reference { - return Reference.fromString(this.data.deleted); + return Reference.fromString(this.data.deleted, this.instanceDomain); } - public static override fromJSON(json: JSONObject): Promise { - return DeleteSchema.parseAsync(json).then((u) => new Delete(u)); + public static override fromJSON( + json: JSONObject, + instanceDomain: string, + ): Promise { + return DeleteSchema.parseAsync(json).then( + (u) => new Delete(u, instanceDomain), + ); } } diff --git a/packages/sdk/entities/entity.ts b/packages/sdk/entities/entity.ts index a21beef6..2cd71149 100644 --- a/packages/sdk/entities/entity.ts +++ b/packages/sdk/entities/entity.ts @@ -4,11 +4,19 @@ import type { JSONObject } from "../types.ts"; export class Entity { public static name = "Entity"; - // biome-ignore lint/suspicious/noExplicitAny: This is a base class that is never instanciated directly - public constructor(public data: any) {} + public constructor( + // biome-ignore lint/suspicious/noExplicitAny: This is a base class that is never instanciated directly + public data: any, + public instanceDomain: string, + ) {} - public static fromJSON(json: JSONObject): Promise { - return EntitySchema.parseAsync(json).then((u) => new Entity(u)); + public static fromJSON( + json: JSONObject, + instanceDomain: string, + ): Promise { + return EntitySchema.parseAsync(json).then( + (u) => new Entity(u, instanceDomain), + ); } public toJSON(): JSONObject { @@ -19,10 +27,19 @@ export class Entity { export class Reference { public constructor( public id: string, - public domain?: string, + public domain: string, ) {} - public static fromString(str: string): Reference { + /** + * Parses a reference from a string. The string can be in the format "domain:id" or just "id" if the domain is the local instance (in which case a default domain must be provided). + * @param str + * @param defaultDomain + * @returns + */ + public static fromString( + str: string, + defaultDomain: URL | string, + ): Reference { // Expect format: domain:id or id (if domain is the local instance) // Handle IPv6 addresses in brackets const chunks = str.split(":"); @@ -36,10 +53,21 @@ export class Reference { return new Reference(id, domain); } - return new Reference(str); + if (!defaultDomain) { + throw new Error( + `Invalid reference string: ${str}. Expected format "domain:id" or "id" with a default domain provided.`, + ); + } + + return new Reference( + str, + defaultDomain instanceof URL + ? defaultDomain.hostname + : defaultDomain, + ); } public toString(): string { - return this.domain ? `${this.domain}:${this.id}` : this.id; + return `${this.domain}:${this.id}`; } } diff --git a/packages/sdk/entities/extensions/likes.ts b/packages/sdk/entities/extensions/likes.ts index 8615b3fc..18460fba 100644 --- a/packages/sdk/entities/extensions/likes.ts +++ b/packages/sdk/entities/extensions/likes.ts @@ -6,39 +6,55 @@ import { Entity, Reference } from "../entity.ts"; export class Like extends Entity { public static override name = "pub.versia:likes/Like"; - public constructor(public override data: z.infer) { - super(data); + public constructor( + public override data: z.infer, + instanceDomain: string, + ) { + super(data, instanceDomain); } public get author(): Reference { - return Reference.fromString(this.data.author); + return Reference.fromString(this.data.author, this.instanceDomain); } public get liked(): Reference { - return Reference.fromString(this.data.liked); + return Reference.fromString(this.data.liked, this.instanceDomain); } - public static override fromJSON(json: JSONObject): Promise { - return LikeSchema.parseAsync(json).then((u) => new Like(u)); + public static override fromJSON( + json: JSONObject, + instanceDomain: string, + ): Promise { + return LikeSchema.parseAsync(json).then( + (u) => new Like(u, instanceDomain), + ); } } export class Dislike extends Entity { public static override name = "pub.versia:likes/Dislike"; - public constructor(public override data: z.infer) { - super(data); + public constructor( + public override data: z.infer, + instanceDomain: string, + ) { + super(data, instanceDomain); } public get author(): Reference { - return Reference.fromString(this.data.author); + return Reference.fromString(this.data.author, this.instanceDomain); } public get disliked(): Reference { - return Reference.fromString(this.data.disliked); + return Reference.fromString(this.data.disliked, this.instanceDomain); } - public static override fromJSON(json: JSONObject): Promise { - return DislikeSchema.parseAsync(json).then((u) => new Dislike(u)); + public static override fromJSON( + json: JSONObject, + instanceDomain: string, + ): Promise { + return DislikeSchema.parseAsync(json).then( + (u) => new Dislike(u, instanceDomain), + ); } } diff --git a/packages/sdk/entities/extensions/polls.ts b/packages/sdk/entities/extensions/polls.ts index 4c508552..02aaf2c8 100644 --- a/packages/sdk/entities/extensions/polls.ts +++ b/packages/sdk/entities/extensions/polls.ts @@ -6,19 +6,27 @@ import { Entity, Reference } from "../entity.ts"; export class Vote extends Entity { public static override name = "pub.versia:polls/Vote"; - public constructor(public override data: z.infer) { - super(data); + public constructor( + public override data: z.infer, + instanceDomain: string, + ) { + super(data, instanceDomain); } public get author(): Reference { - return Reference.fromString(this.data.author); + return Reference.fromString(this.data.author, this.instanceDomain); } public get poll(): Reference { - return Reference.fromString(this.data.poll); + return Reference.fromString(this.data.poll, this.instanceDomain); } - public static override fromJSON(json: JSONObject): Promise { - return VoteSchema.parseAsync(json).then((u) => new Vote(u)); + public static override fromJSON( + json: JSONObject, + instanceDomain: string, + ): Promise { + return VoteSchema.parseAsync(json).then( + (u) => new Vote(u, instanceDomain), + ); } } diff --git a/packages/sdk/entities/extensions/reactions.ts b/packages/sdk/entities/extensions/reactions.ts index 3299c248..e515c48c 100644 --- a/packages/sdk/entities/extensions/reactions.ts +++ b/packages/sdk/entities/extensions/reactions.ts @@ -6,19 +6,27 @@ import { Entity, Reference } from "../entity.ts"; export class Reaction extends Entity { public static override name = "pub.versia:reactions/Reaction"; - public constructor(public override data: z.infer) { - super(data); + public constructor( + public override data: z.infer, + instanceDomain: string, + ) { + super(data, instanceDomain); } public get author(): Reference { - return Reference.fromString(this.data.author); + return Reference.fromString(this.data.author, this.instanceDomain); } public get object(): Reference { - return Reference.fromString(this.data.object); + return Reference.fromString(this.data.object, this.instanceDomain); } - public static override fromJSON(json: JSONObject): Promise { - return ReactionSchema.parseAsync(json).then((u) => new Reaction(u)); + public static override fromJSON( + json: JSONObject, + instanceDomain: string, + ): Promise { + return ReactionSchema.parseAsync(json).then( + (u) => new Reaction(u, instanceDomain), + ); } } diff --git a/packages/sdk/entities/extensions/reports.ts b/packages/sdk/entities/extensions/reports.ts index 31c2ad5e..0ca914cb 100644 --- a/packages/sdk/entities/extensions/reports.ts +++ b/packages/sdk/entities/extensions/reports.ts @@ -6,19 +6,31 @@ import { Entity, Reference } from "../entity.ts"; export class Report extends Entity { public static override name = "pub.versia:reports/Report"; - public constructor(public override data: z.infer) { - super(data); + public constructor( + public override data: z.infer, + instanceDomain: string, + ) { + super(data, instanceDomain); } public get author(): Reference | null { - return this.data.author ? Reference.fromString(this.data.author) : null; + return this.data.author + ? Reference.fromString(this.data.author, this.instanceDomain) + : null; } public get reported(): Reference[] { - return this.data.reported.map((r) => Reference.fromString(r)); + return this.data.reported.map((r) => + Reference.fromString(r, this.instanceDomain), + ); } - public static override fromJSON(json: JSONObject): Promise { - return ReportSchema.parseAsync(json).then((u) => new Report(u)); + public static override fromJSON( + json: JSONObject, + instanceDomain: string, + ): Promise { + return ReportSchema.parseAsync(json).then( + (u) => new Report(u, instanceDomain), + ); } } diff --git a/packages/sdk/entities/extensions/share.ts b/packages/sdk/entities/extensions/share.ts index ea2fb8ad..9846a06a 100644 --- a/packages/sdk/entities/extensions/share.ts +++ b/packages/sdk/entities/extensions/share.ts @@ -6,19 +6,27 @@ import { Entity, Reference } from "../entity.ts"; export class Share extends Entity { public static override name = "pub.versia:share/Share"; - public constructor(public override data: z.infer) { - super(data); + public constructor( + public override data: z.infer, + instanceDomain: string, + ) { + super(data, instanceDomain); } public get author(): Reference { - return Reference.fromString(this.data.author); + return Reference.fromString(this.data.author, this.instanceDomain); } public get shared(): Reference { - return Reference.fromString(this.data.shared); + return Reference.fromString(this.data.shared, this.instanceDomain); } - public static override fromJSON(json: JSONObject): Promise { - return ShareSchema.parseAsync(json).then((u) => new Share(u)); + public static override fromJSON( + json: JSONObject, + instanceDomain: string, + ): Promise { + return ShareSchema.parseAsync(json).then( + (u) => new Share(u, instanceDomain), + ); } } diff --git a/packages/sdk/entities/follow.ts b/packages/sdk/entities/follow.ts index 9ba9db5f..389c180b 100644 --- a/packages/sdk/entities/follow.ts +++ b/packages/sdk/entities/follow.ts @@ -11,20 +11,28 @@ import { Entity, Reference } from "./entity.ts"; export class Follow extends Entity { public static override name = "Follow"; - public constructor(public override data: z.infer) { - super(data); + public constructor( + public override data: z.infer, + instanceDomain: string, + ) { + super(data, instanceDomain); } public get author(): Reference { - return Reference.fromString(this.data.author); + return Reference.fromString(this.data.author, this.instanceDomain); } public get followee(): Reference { - return Reference.fromString(this.data.followee); + return Reference.fromString(this.data.followee, this.instanceDomain); } - public static override fromJSON(json: JSONObject): Promise { - return FollowSchema.parseAsync(json).then((u) => new Follow(u)); + public static override fromJSON( + json: JSONObject, + instanceDomain: string, + ): Promise { + return FollowSchema.parseAsync(json).then( + (u) => new Follow(u, instanceDomain), + ); } } @@ -33,21 +41,25 @@ export class FollowAccept extends Entity { public constructor( public override data: z.infer, + instanceDomain: string, ) { - super(data); + super(data, instanceDomain); } public get author(): Reference { - return Reference.fromString(this.data.author); + return Reference.fromString(this.data.author, this.instanceDomain); } public get follower(): Reference { - return Reference.fromString(this.data.follower); + return Reference.fromString(this.data.follower, this.instanceDomain); } - public static override fromJSON(json: JSONObject): Promise { + public static override fromJSON( + json: JSONObject, + instanceDomain: string, + ): Promise { return FollowAcceptSchema.parseAsync(json).then( - (u) => new FollowAccept(u), + (u) => new FollowAccept(u, instanceDomain), ); } } @@ -57,21 +69,25 @@ export class FollowReject extends Entity { public constructor( public override data: z.infer, + instanceDomain: string, ) { - super(data); + super(data, instanceDomain); } public get author(): Reference { - return Reference.fromString(this.data.author); + return Reference.fromString(this.data.author, this.instanceDomain); } public get follower(): Reference { - return Reference.fromString(this.data.follower); + return Reference.fromString(this.data.follower, this.instanceDomain); } - public static override fromJSON(json: JSONObject): Promise { + public static override fromJSON( + json: JSONObject, + instanceDomain: string, + ): Promise { return FollowRejectSchema.parseAsync(json).then( - (u) => new FollowReject(u), + (u) => new FollowReject(u, instanceDomain), ); } } @@ -79,19 +95,27 @@ export class FollowReject extends Entity { export class Unfollow extends Entity { public static override name = "Unfollow"; - public constructor(public override data: z.infer) { - super(data); + public constructor( + public override data: z.infer, + instanceDomain: string, + ) { + super(data, instanceDomain); } public get author(): Reference { - return Reference.fromString(this.data.author); + return Reference.fromString(this.data.author, this.instanceDomain); } public get followee(): Reference { - return Reference.fromString(this.data.followee); + return Reference.fromString(this.data.followee, this.instanceDomain); } - public static override fromJSON(json: JSONObject): Promise { - return UnfollowSchema.parseAsync(json).then((u) => new Unfollow(u)); + public static override fromJSON( + json: JSONObject, + instanceDomain: string, + ): Promise { + return UnfollowSchema.parseAsync(json).then( + (u) => new Unfollow(u, instanceDomain), + ); } } diff --git a/packages/sdk/entities/instancemetadata.ts b/packages/sdk/entities/instancemetadata.ts index d95f44ab..4b535dc2 100644 --- a/packages/sdk/entities/instancemetadata.ts +++ b/packages/sdk/entities/instancemetadata.ts @@ -10,7 +10,7 @@ export class InstanceMetadata extends Entity { public constructor( public override data: z.infer, ) { - super(data); + super(data, data.domain); } public get logo(): ImageContentFormat | undefined { diff --git a/packages/sdk/entities/note.ts b/packages/sdk/entities/note.ts index 3a84cb68..88d6d811 100644 --- a/packages/sdk/entities/note.ts +++ b/packages/sdk/entities/note.ts @@ -7,16 +7,24 @@ import { Entity, Reference } from "./entity.ts"; export class Note extends Entity { public static override name = "Note"; - public constructor(public override data: z.infer) { - super(data); + public constructor( + public override data: z.infer, + instanceDomain: string, + ) { + super(data, instanceDomain); } - public static override fromJSON(json: JSONObject): Promise { - return NoteSchema.parseAsync(json).then((n) => new Note(n)); + public static override fromJSON( + json: JSONObject, + instanceDomain: string, + ): Promise { + return NoteSchema.parseAsync(json).then( + (n) => new Note(n, instanceDomain), + ); } public get author(): Reference { - return Reference.fromString(this.data.author); + return Reference.fromString(this.data.author, this.instanceDomain); } public get group(): Reference | null { @@ -27,20 +35,24 @@ export class Note extends Entity { return null; } - return Reference.fromString(this.data.group); + return Reference.fromString(this.data.group, this.instanceDomain); } public get mentions(): Reference[] { - return this.data.mentions.map((m) => Reference.fromString(m)); + return this.data.mentions.map((m) => + Reference.fromString(m, this.instanceDomain), + ); } public get quotes(): Reference | null { - return this.data.quotes ? Reference.fromString(this.data.quotes) : null; + return this.data.quotes + ? Reference.fromString(this.data.quotes, this.instanceDomain) + : null; } public get repliesTo(): Reference | null { return this.data.replies_to - ? Reference.fromString(this.data.replies_to) + ? Reference.fromString(this.data.replies_to, this.instanceDomain) : null; } diff --git a/packages/sdk/entities/user.ts b/packages/sdk/entities/user.ts index 0d744632..c83d88cc 100644 --- a/packages/sdk/entities/user.ts +++ b/packages/sdk/entities/user.ts @@ -7,12 +7,20 @@ import { Entity } from "./entity.ts"; export class User extends Entity { public static override name = "User"; - public constructor(public override data: z.infer) { - super(data); + public constructor( + public override data: z.infer, + instanceDomain: string, + ) { + super(data, instanceDomain); } - public static override fromJSON(json: JSONObject): Promise { - return UserSchema.parseAsync(json).then((u) => new User(u)); + public static override fromJSON( + json: JSONObject, + instanceDomain: string, + ): Promise { + return UserSchema.parseAsync(json).then( + (u) => new User(u, instanceDomain), + ); } public get avatar(): ImageContentFormat | undefined { diff --git a/packages/sdk/http.ts b/packages/sdk/http.ts index e87aef6a..dd40600b 100644 --- a/packages/sdk/http.ts +++ b/packages/sdk/http.ts @@ -75,7 +75,7 @@ export class FederationRequester { ); } - const entity = await entityType.fromJSON(jsonData); + const entity = await entityType.fromJSON(jsonData, url.hostname); return entity as InstanceType; } @@ -150,7 +150,10 @@ export class FederationRequester { entities.push( ...collection.data.items.map( (item) => - collectionItemType.fromJSON(item) as InstanceType, + collectionItemType.fromJSON( + item, + reference.domain, + ) as InstanceType, ), ); limit -= collection.data.items.length; diff --git a/packages/sdk/inbox-processor.ts b/packages/sdk/inbox-processor.ts index 1fee5da1..59cd74cf 100644 --- a/packages/sdk/inbox-processor.ts +++ b/packages/sdk/inbox-processor.ts @@ -19,7 +19,10 @@ type MaybePromise = T | Promise; export class EntitySorter { private readonly handlers: EntitySorterHandlers = new Map(); - public constructor(private readonly jsonData: JSONObject) {} + public constructor( + private readonly jsonData: JSONObject, + public instanceDomain: string, + ) {} public on( entity: T, @@ -45,7 +48,7 @@ export class EntitySorter { if (entity) { await this.handlers.get(entity)?.( - await entity.fromJSON(this.jsonData), + await entity.fromJSON(this.jsonData, this.instanceDomain), ); } else { await defaultHandler?.();