Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions docs/manual/context.md
Original file line number Diff line number Diff line change
Expand Up @@ -435,20 +435,33 @@ The returned WebFinger document contains links to various resources associated
with the account, such as profile pages, ActivityPub actor URIs, and more:

~~~~ typescript twoslash
import { type Context } from "@fedify/fedify";
import { type Context, isLink, isOStatusSubscribeLink } from "@fedify/fedify";
const ctx = null as unknown as Context<void>;
// ---cut-before---
const webfingerData = await ctx.lookupWebFinger("acct:fedify@hollo.social");

// Find the ActivityPub actor URI
const activityPubActorLink = webfingerData?.links?.find(link =>
link.rel === "self" && link.type === "application/activity+json"
const activityPubActorLink = webfingerData?.links?.find(
isLink.withCondition((link) =>
link.rel === "self" &&
link.type === "application/activity+json"
),
);

if (activityPubActorLink?.href) {
const actor = await ctx.lookupObject(activityPubActorLink.href);
// Work with the actor...
}

// Get subscription template URI
const activityPubSubscribeLink = webfingerData?.links?.find((link) =>
isOStatusSubscribeLink(link)
);

if (activityPubSubscribeLink?.template) {
// Work with remote follow URI...
}

~~~~

> [!NOTE]
Expand Down
7 changes: 6 additions & 1 deletion packages/fedify/src/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,9 @@ export * from "./nodeinfo/mod.ts";
export * from "./runtime/mod.ts";
export * from "./sig/mod.ts";
export * from "./vocab/mod.ts";
export { lookupWebFinger, type ResourceDescriptor } from "./webfinger/mod.ts";
export {
isLink,
isOStatusSubscribeLink,
lookupWebFinger,
type ResourceDescriptor,
} from "./webfinger/mod.ts";
20 changes: 11 additions & 9 deletions packages/fedify/src/nodeinfo/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
getUserAgent,
type GetUserAgentOptions,
} from "../runtime/docloader.ts";
import type { ResourceDescriptor } from "../webfinger/jrd.ts";
import { isLink, type ResourceDescriptor } from "../webfinger/jrd.ts";
import { parseSemVer, type SemVer } from "./semver.ts";
import type {
InboundService,
Expand Down Expand Up @@ -113,15 +113,17 @@ export async function getNodeInfo(
return undefined;
}
const wellKnownRd = await wellKnownResponse.json() as ResourceDescriptor;
const link = wellKnownRd?.links?.find((link) =>
link != null &&
"rel" in link &&
(link.rel === "http://nodeinfo.diaspora.software/ns/schema/2.0" ||
link.rel === "http://nodeinfo.diaspora.software/ns/schema/2.1") &&
"href" in link &&
link.href != null
const link = wellKnownRd?.links?.find(
isLink.withCondition((link) =>
link != null &&
"rel" in link &&
(link.rel === "http://nodeinfo.diaspora.software/ns/schema/2.0" ||
link.rel === "http://nodeinfo.diaspora.software/ns/schema/2.1") &&
"href" in link &&
link.href != null
),
);
if (link == null) {
if (link == null || link.href == null) {
logger.error(
"Failed to find a NodeInfo document link from {url}: {resourceDescriptor}",
{ url: wellKnownUrl.href, resourceDescriptor: wellKnownRd },
Expand Down
5 changes: 4 additions & 1 deletion packages/fedify/src/vocab/lookup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
getDocumentLoader,
type GetUserAgentOptions,
} from "../runtime/docloader.ts";
import { isOStatusSubscribeLink } from "../webfinger/jrd.ts";
import { lookupWebFinger } from "../webfinger/lookup.ts";
import { toAcctUrl } from "./handle.ts";
import { getTypeId } from "./type.ts";
Expand Down Expand Up @@ -158,10 +159,12 @@ async function lookupObjectInternal(
if (jrd?.links == null) return null;
for (const l of jrd.links) {
if (
isOStatusSubscribeLink(l) ||
l.type !== "application/activity+json" &&
!l.type?.match(
/application\/ld\+json;\s*profile="https:\/\/www.w3.org\/ns\/activitystreams"/,
) || l.rel !== "self"
) ||
l.rel !== "self" || l.href == null
) continue;
try {
const remoteDoc = await documentLoader(l.href, {
Expand Down
53 changes: 51 additions & 2 deletions packages/fedify/src/webfinger/jrd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export interface ResourceDescriptor {
/**
* Links to other resources.
*/
links?: Link[];
links?: Array<Link | OStatusSubscribeLink>;
Comment thread
dodok8 marked this conversation as resolved.
}

/**
Expand All @@ -44,7 +44,7 @@ export interface Link {
/**
* A URI pointing to the target resource.
*/
href: string;
href?: string;
Comment thread
dodok8 marked this conversation as resolved.

/**
* Human-readable titles describing the link relation. If the language is
Expand All @@ -57,3 +57,52 @@ export interface Link {
*/
properties?: Record<string, string>;
}

/**
* References a link. See also
* [OStatus 1.0 Draft 2](https://www.w3.org/community/ostatus/wiki/images/9/93/OStatus_1.0_Draft_2.pdf)
*/
export interface OStatusSubscribeLink {
rel: "http://ostatus.org/schema/1.0/subscribe";
/**
* A URI template (RFC 6570) that can be used to construct URIs by
* substituting variables. Used primarily for subscription endpoints
* where parameters like account URIs need to be dynamically inserted.
*/
template: string;
}
Comment thread
dodok8 marked this conversation as resolved.
Comment on lines +61 to +73

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of declaring a separate OStatusSubscribeLink type, how about adding a template field to the existing Link type? I think the template field might be useful even when the rel field is not "http://ostatus.org/schema/1.0/subscribe".

@dodok8 dodok8 Sep 2, 2025

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This way seems right; however, this PR and branch are mainly about separating types, so I'll close this PR and make a new PR after adding the template field and fixing the following problems.


export function isOStatusSubscribeLink(
link: Link | OStatusSubscribeLink,
): link is OStatusSubscribeLink {
if (
"template" in link &&
link.rel === "http://ostatus.org/schema/1.0/subscribe" &&
typeof link.template === "string"
) return true;
return false;
}

isOStatusSubscribeLink.withCondition = (
condition: (link: OStatusSubscribeLink) => boolean,
) =>
(link: Link | OStatusSubscribeLink): link is OStatusSubscribeLink => {
return isOStatusSubscribeLink(link) && condition(link);
};

export function isLink(
link: Link | OStatusSubscribeLink,
): link is Link {
if (
"rel" in link &&
typeof link.rel === "string"
) return true;
return false;
}

isLink.withCondition = (
condition: (link: Link) => boolean,
) =>
(link: Link | Link): link is Link => {
return isLink(link) && condition(link);
};
Loading