Statusphere is a sample application demonstrating how to use the Laravel Bluesky package to integrate with the Bluesky social network. It follows the official AT Protocol application guidelines.
- Laravel 12.x
- Tailwind 4.x
- Livewire 3.x / Volt 1.6.x
- laravel-bluesky 1.x
- No WebSocket/Firehose (Many PHP developers are not familiar with using supervisor, so WebSockets are not used)
Statusphere demonstrates key AT Protocol application patterns by implementing a decentralized emoji status system. The application follows the official AT Protocol application guidelines using Laravel and the laravel-bluesky package.
Users authenticate using their Bluesky credentials via OAuth flow:
- Login Route: Users enter their Bluesky handle (e.g.,
alice.bsky.social) - OAuth Flow: Laravel Socialite with Bluesky driver handles the authentication
- Session Management: OAuth tokens are stored in Laravel sessions for API access
- User Creation: User records are created/updated with DID, handle, and profile information
The authentication is handled in the /login and /callback routes, which create an OAuthSession that provides authenticated access to the user's AT Protocol repository.
Statusphere publishes emoji status records using a custom AT Protocol schema:
- Collection:
com.puklipo.statusphere.status - Schema: Records contain
status(emoji) andcreatedAt(timestamp) - Record Keys: Uses TID (Timestamp Identifier) for unique, time-ordered keys
- Validation: Laravel validation ensures only valid emojis from the configured list
The status records are defined in the Status class using the laravel-bluesky package's Recordable interface.
When users select an emoji status:
- Validation: Emoji is validated against the configured list in
config/statusphere.php - OAuth Check: Session tokens are verified and refreshed if needed
- Record Creation: A new status record is created with the selected emoji
- AT Protocol Write: Record is published to the user's repository using
Bluesky::putRecord() - UI Update: Livewire components update reactively to show the new status
The publishing flow is implemented in the create-status Livewire component, which provides real-time feedback and error handling.
The application retrieves and displays user information:
- Profile Data: Fetches user profiles from their repositories for display names and avatars
- Status History: Retrieves the user's previous status records using
Bluesky::listRecords() - Real-time Updates: Livewire provides reactive updates when new statuses are created
- Timeline Display: Shows chronological status updates with user attribution
User data is accessed through the authenticated OAuth session, allowing read access to the user's AT Protocol repository.
The application architecture is designed with consideration for AT Protocol event processing:
- OAuth-Focused: The primary implementation uses OAuth-based user interactions
- No WebSockets: As noted in the Technology Stack section, WebSockets/Firehose are intentionally not used
- Reference Implementation: The codebase includes reference patterns for those who wish to implement event processing
- Future Extensibility: The architecture allows for adding Bluesky Firehose integration if needed
Note: The current implementation prioritizes simplicity and accessibility for PHP developers who may not be familiar with WebSocket infrastructure.
- Local Development: OAuth posting is disabled locally; use
php artisan bsky:create-statuscommand instead - Error Handling: Comprehensive error display for OAuth and API failures
- Validation: Both client-side and server-side validation of emoji selections
- Session Management: Automatic token refresh and re-authentication when needed
git clone https://github.com/invokable/statusphere.git
cd statusphere
cp .env.example .env
composer install
composer run post-create-project-cmd
npm install
npm run buildFor Bluesky OAuth authentication, you need to create a private key. This is required before using any OAuth functionality and is the only configuration needed (no client_id or client_secret registration with Bluesky is required).
Generate a new private key with:
php artisan bluesky:new-private-keyThe command will output a URL-safe base64 encoded key like this:
Please set this private key in .env
BLUESKY_OAUTH_PRIVATE_KEY="...url-safe base64 encoded key..."
Copy and paste this key into your .env file:
// .env
BLUESKY_OAUTH_PRIVATE_KEY="..."
Due to OAuth limitations, posting from local environments is not supported with OAuth authentication. Instead, you need to use App Password authentication for local development and testing. Configure your Bluesky account credentials:
// .env
BLUESKY_IDENTIFIER=***.bsky.social
BLUESKY_APP_PASSWORD=****-****-****-****
For local development, use the following command to create status updates:
php artisan bsky:create-statusThis command uses App Password authentication to post status updates to your Bluesky account.
Finally, start with the Laravel local server command. You can view it at http://localhost:8000.
php artisan serveIf you want to create a more official-like implementation, you can also use WebSockets to receive and store data. The laravel-bluesky package supports both Firehose and Jetstream, but Jetstream is easier to use when you only need data from specific collections like in this case. Note that this Jetstream is Bluesky's Jetstream, not Laravel's Jetstream with the same name, which can be confusing. You can check what kind of data is received on GitHub: https://github.com/bluesky-social/jetstream
Run the command specifying the collection continuously using Supervisor:
php artisan bluesky:ws start -C com.puklipo.statusphere.statusWhen data is received, a JetstreamCommitMessage event is fired, which can be captured by a Listener and saved to the database:
php artisan make:listener StatusListenernamespace App\Listeners;
use App\Record\Status;
use Revolution\Bluesky\Events\Jetstream\JetstreamCommitMessage;
class StatusListener
{
/**
* Create the event listener.
*/
public function __construct()
{
//
}
/**
* Handle the event.
*/
public function handle(JetstreamCommitMessage $event): void
{
if ($event->operation !== 'create') {
return;
}
$collection = data_get($event->message, 'commit.collection');
if ($collection !== Status::NSID) {
return;
}
$did = data_get($event->message, 'did');
$status = data_get($event->message, 'commit.record.status');
$createdAt = data_get($event->message, 'commit.record.createdAt');
// Save status to database
}
}If you actually use this approach, other parts of the application would need to be modified as well. This is just for explanation purposes in the advanced section, so no further implementation is provided.
When deploying to production, terminate with stop and configure Supervisor to automatically restart:
php artisan bluesky:ws stopMIT
