Loading CHANGES.md +9 −0 Original line number Diff line number Diff line Loading @@ -31,6 +31,13 @@ To be released. - Added `MemoryKvStore.cas()` method. - Added `DenoKvStore.cas()` method. - Added options to customize the temporary actor information when running `fedify inbox` command. [[#262], [#285] by Hasang Cho] - Added `--actor-name` option to customize the actor display name. - Added `--actor-summary` option to customize the actor description. - Both options provide sensible defaults when not specified. - Added useful functions for fediverse handles at `@fedify/fedify/vocab`. This functions simplify working with fediverse handles and URLs. [[#278] by ChanHaeng Lee] Loading Loading @@ -63,9 +70,11 @@ To be released. [#168]: https://github.com/fedify-dev/fedify/issues/168 [#248]: https://github.com/fedify-dev/fedify/issues/248 [#260]: https://github.com/fedify-dev/fedify/issues/260 [#262]: https://github.com/fedify-dev/fedify/issues/262 [#278]: https://github.com/fedify-dev/fedify/pull/278 [#281]: https://github.com/fedify-dev/fedify/pull/281 [#282]: https://github.com/fedify-dev/fedify/pull/282 [#285]: https://github.com/fedify-dev/fedify/pull/285 Version 1.7.4 Loading cli/inbox.tsx +113 −54 Original line number Diff line number Diff line Loading @@ -35,7 +35,25 @@ import { recordingSink } from "./log.ts"; import { tableStyle } from "./table.ts"; import { spawnTemporaryServer, type TemporaryServer } from "./tempserver.ts"; const logger = getLogger(["fedify", "cli", "inbox"]); /** * Context data for the ephemeral ActivityPub inbox server. * * This interface defines the shape of context data passed to federation * handlers during inbox command execution. */ interface ContextData { activityIndex: number; actorName: string; actorSummary: string; } /** * Options for actor customization. */ interface ActorOptions { actorName: string; actorSummary: string; } /** * Options for the inbox command. Loading @@ -53,6 +71,8 @@ export const TunnelConfig = { }, } as const; const logger = getLogger(["fedify", "cli", "inbox"]); export const command = new Command() .description( "Spins up an ephemeral server that serves the ActivityPub inbox with " + Loading @@ -77,7 +97,20 @@ export const command = new Command() "-T, --no-tunnel", "Do not tunnel the ephemeral ActivityPub server to the public Internet.", ) .action(async (options: InboxOptions) => { .option( "--actor-name=<name:string>", "Customize the actor display name.", { default: "Fedify Ephemeral Inbox" }, ) .option( "--actor-summary=<summary:string>", "Customize the actor description.", { default: "An ephemeral ActivityPub inbox for testing purposes." }, ) .action(async (options: InboxOptions & ActorOptions) => { const fetch = createFetchHandler(options); const sendDeleteToPeers = createSendDeleteToPeers(options); const spinner = ora({ text: "Spinning up an ephemeral ActivityPub server...", discardStdin: false, Loading Loading @@ -109,7 +142,13 @@ export const command = new Command() }); }); spinner.start(); const fedCtx = federation.createContext(server.url, -1); const fedCtx = federation.createContext(server.url, { activityIndex: -1, actorName: options.actorName, actorSummary: options.actorSummary, }); if (options.acceptFollow != null && options.acceptFollow.length > 0) { acceptFollows.push(...(options.acceptFollow ?? [])); } Loading Loading @@ -144,7 +183,7 @@ export const command = new Command() printServerInfo(fedCtx); }); const federation = createFederation<number>({ const federation = createFederation<ContextData>({ kv: new MemoryKvStore(), documentLoader: await getDocumentLoader(), }); Loading @@ -158,8 +197,8 @@ federation return new Application({ id: ctx.getActorUri(identifier), preferredUsername: identifier, name: "Fedify Ephemeral Inbox", summary: "An ephemeral ActivityPub inbox for testing purposes.", name: ctx.data.actorName, summary: ctx.data.actorSummary, inbox: ctx.getInboxUri(identifier), endpoints: new Endpoints({ sharedInbox: ctx.getInboxUri(), Loading Loading @@ -212,8 +251,18 @@ async function acceptsFollowFrom(actor: Actor): Promise<boolean> { const peers: Record<string, Actor> = {}; async function sendDeleteToPeers(server: TemporaryServer): Promise<void> { const ctx = federation.createContext(new Request(server.url), -1); function createSendDeleteToPeers( actorOptions: ActorOptions, ): (server: TemporaryServer) => Promise<void> { return async function sendDeleteToPeers( server: TemporaryServer, ): Promise<void> { const ctx = federation.createContext(new Request(server.url), { activityIndex: -1, actorName: actorOptions.actorName, actorSummary: actorOptions.actorSummary, }); const actor = (await ctx.getActor("i"))!; try { await ctx.sendActivity( Loading @@ -232,6 +281,7 @@ async function sendDeleteToPeers(server: TemporaryServer): Promise<void> { { error }, ); } }; } const followers: Record<string, Actor> = {}; Loading @@ -240,7 +290,7 @@ federation .setInboxListeners("/{identifier}/inbox", "/inbox") .setSharedKeyDispatcher((_) => ({ identifier: "i" })) .on(Activity, async (ctx, activity) => { activities[ctx.data].activity = activity; activities[ctx.data.activityIndex].activity = activity; for await (const actor of activity.getActors()) { if (actor.id != null) peers[actor.id.href] = actor; } Loading Loading @@ -325,7 +375,7 @@ federation.setNodeInfoDispatcher("/nodeinfo/2.1", (_ctx) => { }; }); function printServerInfo(fedCtx: Context<number>): void { function printServerInfo(fedCtx: Context<ContextData>): void { new Table( [ new Cell("Actor handle:").align("right"), Loading Loading @@ -427,21 +477,29 @@ app.get("/r/:idx{[0-9]+}", (c) => { ); }); async function fetch(request: Request): Promise<Response> { function createFetchHandler( actorOptions: ActorOptions, ): (request: Request) => Promise<Response> { return async function fetch(request: Request): Promise<Response> { const timestamp = Temporal.Now.instant(); const idx = activities.length; const pathname = new URL(request.url).pathname; if (pathname === "/r" || pathname.startsWith("/r/")) { return app.fetch(request); } const inboxRequest = pathname === "/inbox" || pathname.startsWith("/i/inbox"); const inboxRequest = pathname === "/inbox" || pathname.startsWith("/i/inbox"); if (inboxRequest) { recordingSink.startRecording(); // @ts-ignore: Work around `deno publish --dry-run` bug activities.push({ timestamp, request: request.clone(), logs: [] }); } const response = await federation.fetch(request, { contextData: inboxRequest ? idx : -1, contextData: { activityIndex: inboxRequest ? idx : -1, actorName: actorOptions.actorName, actorSummary: actorOptions.actorSummary, }, onNotAcceptable: app.fetch.bind(app), onNotFound: app.fetch.bind(app), onUnauthorized: app.fetch.bind(app), Loading @@ -453,4 +511,5 @@ async function fetch(request: Request): Promise<Response> { printActivityEntry(idx, activities[idx]); } return response; }; } Loading
CHANGES.md +9 −0 Original line number Diff line number Diff line Loading @@ -31,6 +31,13 @@ To be released. - Added `MemoryKvStore.cas()` method. - Added `DenoKvStore.cas()` method. - Added options to customize the temporary actor information when running `fedify inbox` command. [[#262], [#285] by Hasang Cho] - Added `--actor-name` option to customize the actor display name. - Added `--actor-summary` option to customize the actor description. - Both options provide sensible defaults when not specified. - Added useful functions for fediverse handles at `@fedify/fedify/vocab`. This functions simplify working with fediverse handles and URLs. [[#278] by ChanHaeng Lee] Loading Loading @@ -63,9 +70,11 @@ To be released. [#168]: https://github.com/fedify-dev/fedify/issues/168 [#248]: https://github.com/fedify-dev/fedify/issues/248 [#260]: https://github.com/fedify-dev/fedify/issues/260 [#262]: https://github.com/fedify-dev/fedify/issues/262 [#278]: https://github.com/fedify-dev/fedify/pull/278 [#281]: https://github.com/fedify-dev/fedify/pull/281 [#282]: https://github.com/fedify-dev/fedify/pull/282 [#285]: https://github.com/fedify-dev/fedify/pull/285 Version 1.7.4 Loading
cli/inbox.tsx +113 −54 Original line number Diff line number Diff line Loading @@ -35,7 +35,25 @@ import { recordingSink } from "./log.ts"; import { tableStyle } from "./table.ts"; import { spawnTemporaryServer, type TemporaryServer } from "./tempserver.ts"; const logger = getLogger(["fedify", "cli", "inbox"]); /** * Context data for the ephemeral ActivityPub inbox server. * * This interface defines the shape of context data passed to federation * handlers during inbox command execution. */ interface ContextData { activityIndex: number; actorName: string; actorSummary: string; } /** * Options for actor customization. */ interface ActorOptions { actorName: string; actorSummary: string; } /** * Options for the inbox command. Loading @@ -53,6 +71,8 @@ export const TunnelConfig = { }, } as const; const logger = getLogger(["fedify", "cli", "inbox"]); export const command = new Command() .description( "Spins up an ephemeral server that serves the ActivityPub inbox with " + Loading @@ -77,7 +97,20 @@ export const command = new Command() "-T, --no-tunnel", "Do not tunnel the ephemeral ActivityPub server to the public Internet.", ) .action(async (options: InboxOptions) => { .option( "--actor-name=<name:string>", "Customize the actor display name.", { default: "Fedify Ephemeral Inbox" }, ) .option( "--actor-summary=<summary:string>", "Customize the actor description.", { default: "An ephemeral ActivityPub inbox for testing purposes." }, ) .action(async (options: InboxOptions & ActorOptions) => { const fetch = createFetchHandler(options); const sendDeleteToPeers = createSendDeleteToPeers(options); const spinner = ora({ text: "Spinning up an ephemeral ActivityPub server...", discardStdin: false, Loading Loading @@ -109,7 +142,13 @@ export const command = new Command() }); }); spinner.start(); const fedCtx = federation.createContext(server.url, -1); const fedCtx = federation.createContext(server.url, { activityIndex: -1, actorName: options.actorName, actorSummary: options.actorSummary, }); if (options.acceptFollow != null && options.acceptFollow.length > 0) { acceptFollows.push(...(options.acceptFollow ?? [])); } Loading Loading @@ -144,7 +183,7 @@ export const command = new Command() printServerInfo(fedCtx); }); const federation = createFederation<number>({ const federation = createFederation<ContextData>({ kv: new MemoryKvStore(), documentLoader: await getDocumentLoader(), }); Loading @@ -158,8 +197,8 @@ federation return new Application({ id: ctx.getActorUri(identifier), preferredUsername: identifier, name: "Fedify Ephemeral Inbox", summary: "An ephemeral ActivityPub inbox for testing purposes.", name: ctx.data.actorName, summary: ctx.data.actorSummary, inbox: ctx.getInboxUri(identifier), endpoints: new Endpoints({ sharedInbox: ctx.getInboxUri(), Loading Loading @@ -212,8 +251,18 @@ async function acceptsFollowFrom(actor: Actor): Promise<boolean> { const peers: Record<string, Actor> = {}; async function sendDeleteToPeers(server: TemporaryServer): Promise<void> { const ctx = federation.createContext(new Request(server.url), -1); function createSendDeleteToPeers( actorOptions: ActorOptions, ): (server: TemporaryServer) => Promise<void> { return async function sendDeleteToPeers( server: TemporaryServer, ): Promise<void> { const ctx = federation.createContext(new Request(server.url), { activityIndex: -1, actorName: actorOptions.actorName, actorSummary: actorOptions.actorSummary, }); const actor = (await ctx.getActor("i"))!; try { await ctx.sendActivity( Loading @@ -232,6 +281,7 @@ async function sendDeleteToPeers(server: TemporaryServer): Promise<void> { { error }, ); } }; } const followers: Record<string, Actor> = {}; Loading @@ -240,7 +290,7 @@ federation .setInboxListeners("/{identifier}/inbox", "/inbox") .setSharedKeyDispatcher((_) => ({ identifier: "i" })) .on(Activity, async (ctx, activity) => { activities[ctx.data].activity = activity; activities[ctx.data.activityIndex].activity = activity; for await (const actor of activity.getActors()) { if (actor.id != null) peers[actor.id.href] = actor; } Loading Loading @@ -325,7 +375,7 @@ federation.setNodeInfoDispatcher("/nodeinfo/2.1", (_ctx) => { }; }); function printServerInfo(fedCtx: Context<number>): void { function printServerInfo(fedCtx: Context<ContextData>): void { new Table( [ new Cell("Actor handle:").align("right"), Loading Loading @@ -427,21 +477,29 @@ app.get("/r/:idx{[0-9]+}", (c) => { ); }); async function fetch(request: Request): Promise<Response> { function createFetchHandler( actorOptions: ActorOptions, ): (request: Request) => Promise<Response> { return async function fetch(request: Request): Promise<Response> { const timestamp = Temporal.Now.instant(); const idx = activities.length; const pathname = new URL(request.url).pathname; if (pathname === "/r" || pathname.startsWith("/r/")) { return app.fetch(request); } const inboxRequest = pathname === "/inbox" || pathname.startsWith("/i/inbox"); const inboxRequest = pathname === "/inbox" || pathname.startsWith("/i/inbox"); if (inboxRequest) { recordingSink.startRecording(); // @ts-ignore: Work around `deno publish --dry-run` bug activities.push({ timestamp, request: request.clone(), logs: [] }); } const response = await federation.fetch(request, { contextData: inboxRequest ? idx : -1, contextData: { activityIndex: inboxRequest ? idx : -1, actorName: actorOptions.actorName, actorSummary: actorOptions.actorSummary, }, onNotAcceptable: app.fetch.bind(app), onNotFound: app.fetch.bind(app), onUnauthorized: app.fetch.bind(app), Loading @@ -453,4 +511,5 @@ async function fetch(request: Request): Promise<Response> { printActivityEntry(idx, activities[idx]); } return response; }; }