Skip to content

@fedify/debugger: Embedded ActivityPub debug dashboard #561

@dahlia

Description

@dahlia

Summary

Create a new @fedify/debugger package that provides an embedded real-time ActivityPub debug dashboard. It works as a proxy implementing the Federation interface, wrapping the original federation object and serving an SSR-based web UI using Hono internally.

This issue is a concrete implementation of #234, based on the architecture decision in #323 (OTel-based approach) and the FedifySpanExporter implemented in #497.

Design

A proxy implementing the Federation interface

The debugger fully implements Federation<TContextData>. It overrides only the fetch() method; all other methods (startQueue, processQueuedTask, createContext, setActorDispatcher, etc.) are delegated to the inner Federation object as-is.

This means it can be used as a drop-in replacement anywhere a Federation object is expected, including existing framework integration packages (@fedify/hono, @fedify/express, @fedify/h3, etc.):

import { createFederationDebugger } from "@fedify/debugger";
import { FedifySpanExporter } from "@fedify/fedify/otel";
import { federation } from "@fedify/hono";
import { Hono } from "hono";
import {
  NodeTracerProvider,
  SimpleSpanProcessor,
} from "@opentelemetry/sdk-trace-node";

const kv = new MemoryKvStore();
const exporter = new FedifySpanExporter(kv, {
  ttl: Temporal.Duration.from({ hours: 1 }),
});

const tracerProvider = new NodeTracerProvider();
tracerProvider.addSpanProcessor(new SimpleSpanProcessor(exporter));

const fed = createFederation({ kv, tracerProvider });

// Wrap federation with debugger:
const dbg = createFederationDebugger(fed, { exporter });

// Use with any framework integration as-is:
const app = new Hono();
app.use(federation(dbg, (ctx) => undefined));

How fetch() works

Request
  │
  ├─ /__debug__/*  →  Hono app (SSR dashboard)
  │                     ├─ GET /              → traces list page
  │                     ├─ GET /traces/:id    → trace detail page
  │                     └─ GET /api/traces    → JSON API (polling)
  │
  └─ everything else →  federation.fetch()

Requests matching the debugger path prefix are handled by the internal Hono app. Everything else is delegated to the original Federation.fetch(). This is implemented by intercepting requests at the point where the onNotFound callback would be invoked.

Internal implementation

Hono is included as a regular dependency (not a peer dependency, since it is an internal implementation detail):

  • Routing: debugger path matching
  • JSX SSR: dashboard page rendering via hono/jsx
  • Static assets: CSS embedded inline

API

interface FederationDebuggerOptions {
  /** The path prefix for the debug dashboard.  Defaults to "/__debug__". */
  path?: string;
  /** The FedifySpanExporter to query trace data from. */
  exporter: FedifySpanExporter;
}

function createFederationDebugger<TContextData>(
  federation: Federation<TContextData>,
  options: FederationDebuggerOptions,
): Federation<TContextData>;

The return type is Federation<TContextData> itself, so it can be used as a drop-in replacement in any context where a Federation object is expected.

MVP scope

Pages

  1. Traces list (GET /__debug__/)

    • Displays recent traces via exporter.getRecentTraces()
    • Shows trace ID (first 8 characters), activity types, timestamp, and activity count for each trace
    • Each trace links to its detail page
  2. Trace detail (GET /__debug__/traces/:traceId)

    • Displays all activities in a trace via exporter.getActivitiesByTraceId(traceId)
    • Shows direction (inbound/outbound), activity type, actor, and timestamp for each activity
    • Expandable activity JSON
    • Signature verification details for inbound activities
    • Target inbox URL for outbound activities

Polling-based updates

  1. JSON API (GET /__debug__/api/traces)
    • Returns getRecentTraces() as JSON
    • The frontend polls this endpoint periodically to refresh the list
    • Implemented with a simple inline <script>

Out of scope (future work)

  • WebSocket/SSE-based real-time streaming
  • Filtering UI by activity type, actor, or direction
  • Retry queue management
  • Authentication/access control for production environments

Package structure

packages/debugger/
├── deno.json
├── package.json
├── tsdown.config.ts
└── src/
    ├── mod.ts                # createFederationDebugger()
    ├── mod.test.ts
    └── views/
        ├── layout.tsx        # HTML layout
        ├── traces-list.tsx   # Traces list page
        └── trace-detail.tsx  # Trace detail page

Dependencies

Dependency Type Reason
@fedify/fedify peerDependencies Federation, FedifySpanExporter
hono dependencies Internal routing and JSX SSR

Metadata

Metadata

Assignees

Relationships

None yet

Development

No branches or pull requests

Issue actions