assemblee-virtuelle/activitypub-bundle
Installation
composer require assemblee-virtuelle/activitypub-bundle
Ensure your config/bundles.php includes:
AssembleeVirtuelle\ActivityPubBundle\ActivityPubBundle::class => ['all' => true],
Configuration
Add to config/packages/activitypub.yaml:
activitypub:
domain: 'https://yourdomain.com'
actor_model: App\Entity\YourActorEntity
inbox_endpoint: '/api/inbox'
outbox_endpoint: '/api/outbox'
First Use Case: Basic Actor Setup
Create an Actor entity (e.g., User or Group) extending ActivityPubBundle's base Actor class:
use AssembleeVirtuelle\ActivityPubBundle\Entity\Actor;
#[ORM\Entity]
class User extends Actor
{
// Your custom fields (e.g., name, email)
}
Enable Routes
The bundle provides default routes for inbox/outbox. Verify in config/routes.yaml:
activitypub_inbox:
path: /api/inbox
controller: AssembleeVirtuelle\ActivityPubBundle\Controller\InboxController::handle
Test with curl
Fetch an actor's profile (replace :id with a valid entity ID):
curl -H "Accept: application/activity+json" https://yourdomain.com/api/actors/:id
ActorManager service to hydrate entities with ActivityPub metadata:
$actorManager = $this->get('activitypub.actor.manager');
$actor = $actorManager->createActor($userEntity);
$actorManager->persist($actor);
ActorFetcher to resolve remote actors by URL:
$fetcher = $this->get('activitypub.actor.fetcher');
$remoteActor = $fetcher->fetch('https://remote.com/users/john');
Create, Like) via the ActivityFactory:
$factory = $this->get('activitypub.activity.factory');
$activity = $factory->createActivity(
'Create',
$actor,
['object' => $postEntity]
);
$activityManager = $this->get('activitypub.activity.manager');
$activityManager->publish($activity);
InboxController to handle incoming activities:
public function handle(Request $request, ActivityManager $activityManager)
{
$activity = $activityManager->processIncoming($request->getContent());
// Custom logic (e.g., store, notify)
}
ActivityPubBundle\Object\Object for domain-specific objects (e.g., Note, Article):
use AssembleeVirtuelle\ActivityPubBundle\Object\Object;
class Post extends Object
{
#[ORM\Column]
private string $content;
public function getType(): string { return 'Note'; }
}
@ActivityPub\Object to auto-map Doctrine entities:
#[ActivityPub\Object(type: 'Note')]
#[ORM\Entity]
class BlogPost { ... }
$followManager = $this->get('activitypub.follow.manager');
$followManager->pollFollowedActors();
ActivityPub\Follow webhooks:
activitypub_webhook:
path: /api/webhook
controller: App\Controller\WebhookController::handleFollow
activitypub.twig.extension to render ActivityPub links:
{{ actor|activitypub_url('inbox') }}
$this->get('messenger')->dispatch(
new PublishActivityMessage($activity)
);
# config/packages/cache.yaml
frameworks:
cache:
app.activitypub: ~
ActivityPubBundle\Batch\ActivityBatch for bulk operations (e.g., fetching multiple actors).ActivityPubBundle\Test\ActorFactory for unit tests:
$actor = $this->get('activitypub.test.actor_factory')->create();
HttpClient:
$response = $client->request('POST', '/api/inbox', [
'headers' => ['Content-Type' => 'application/activity+json'],
'body' => json_encode($activityData),
]);
ActivityPubBundle\Storage\StorageInterface to swap implementations.id or url fields in actors/activities can break federation.Actor and Object entities implement getId() and getUrl() correctly:
public function getId(): string { return $this->url; } // Must be absolute URL
https://example.com) may fail silently.activitypub.yaml:
activitypub:
debug:
validate_signatures: true
ActivityPubBundle\Validator\SignatureValidatorInterface for custom logic.RateLimiter or add middleware:
$limiter = $this->get('rate_limiter.activitypub');
if (!$limiter->isAllowed('fetch_actor', 10, 60)) {
throw new \RuntimeException('Rate limit exceeded');
}
type (e.g., Announce) may not be processed.ActivityPubBundle\Activity\Activity or register custom handlers:
# config/packages/activitypub.yaml
activitypub:
activity_handlers:
Announce: App\Handler\AnnounceHandler
Add to config/packages/monolog.yaml:
handlers:
activitypub:
type: stream
path: "%kernel.logs_dir%/activitypub.log"
level: debug
channels: ["activitypub"]
Use JSON-LD Playground to validate serialized output:
$serializer = $this->get('activitypub.serializer');
$jsonLd = $serializer->serialize($activity, 'json-ld');
Ensure responses include:
Content-Type: application/activity+json
Link: <https://yourdomain.com>; rel="http://www.w3.org/ns/activitystreams#public"
InvalidActorException: Actor entity lacks required fields (@context, id, type).SignatureVerificationFailed: Remote activity lacks or has invalid signature.UnknownActivityType: Activity type not registered in the bundle.Extend ActivityPubBundle\Activity\Activity or create a new handler:
namespace App\Activity;
use AssembleeVirtuelle\ActivityPubBundle\Activity\Activity;
class CustomActivity extends Activity
{
public function getType():
How can I help you explore Laravel packages today?