revolution/laravel-bluesky
A Laravel package for integrating Bluesky and the AT Protocol into your app. Connect accounts, publish posts, and interact with the Bluesky API through a clean, framework-friendly wrapper with simple configuration and service provider support.
Architecture fit. The package is still idiomatic Laravel — service provider, container-bound singleton, facade, publishable config — but the 1.1.0 release materially changes its shape. Three of the four release items add capability outside the original "authenticate and post" frame: a BlueskyProvider (a Socialite-style OAuth driver, evidenced by the transition:email scope), a FirehoseSyncMessage event with a sync handler (an inbound, long-running consumer of the AT Protocol firehose), and activity subscription methods on AppBskyNotification (subscription-based notification delivery rather than polling). The earlier framing of "one composer dependency plus a config block" no longer covers the whole surface. The write path still fits the Controller → Job → Facade layering with no new patterns, but the firehose path introduces a genuinely different deployment shape: a supervised long-lived process with cursor persistence and backfill semantics, which is closer to a queue consumer or websocket worker than to a request-scoped singleton. That is a real architectural change and should be scoped as a separate workstream, not folded into the publishing integration.
Integration feasibility. The feasibility picture improves on one axis and worsens on another. Improved: the BlueskyProvider and the transition:email scope indicate the package now supports the OAuth/DPoP flow rather than only handle + app password. If that holds, the earlier blockers — no reusable OAuth flow, no SSO tie-in, per-account credential provisioning — soften considerably, and multi-tenant publishing moves from "noticeably lower feasibility" toward plausible. The DPoP proof handling and JWT exp claim work is consistent with that reading and is the kind of thing we would otherwise have had to build and get right ourselves. Worsened: the firehose and notification-subscription features are not a composer require away from working — they require a running consumer, durable cursor state, and a subscription lifecycle, which is infrastructure we do not currently have. Feasibility is therefore high for the write path (as before, and now cheaper if OAuth is genuinely supported) and moderate-to-low for the read path, which should be treated as a new integration rather than an extension of the existing one.
Technical risk. The dominant risk profile has shifted from "thin wrapper may fall behind" to "larger surface, still single-maintainer." The package now contains security-sensitive cryptography (DPoP proof construction, JWT claims) and long-lived streaming logic — both areas where subtle bugs are expensive and where a 44-star, zero-dependent package gives us no downstream signal that anyone is exercising them in production. A DPoP exp or proof-URL defect manifests as intermittent auth failures that are hard to reproduce and easy to misattribute to our own code; a firehose reconnect defect manifests as data gaps discovered weeks later. The protocol-drift risk is unchanged in kind but larger in scope: AT Protocol OAuth and the firehose/Jetstream surface move at least as fast as the posting lexicon did. Secondary risks carry over — illuminate/* constraints across Laravel majors, rate-limit handling left to the caller — plus two new ones: DPoP private-key lifecycle (a new secret class to store and rotate, distinct from app passwords) and cursor/state durability for the sync handler. The mitigating factor remains that the codebase is small enough to read and fork, but "small enough to read" is now a weaker claim than it was.
Key questions.
BlueskyProvider implement a full OAuth authorization-code + DPoP flow usable from Laravel Socialite, and can it coexist with the existing app-password path during transition?FirehoseSyncMessage a true firehose consumer (full network stream) or a filtered/subscription stream, and does it persist a cursor for resume-after-failure and backfill?AppBskyNotification subscription methods require us to register and renew subscriptions, and what happens to delivery if renewal lapses?illuminate/* constraint range, and are the new features optional or do they pull new dependencies?Stack fit. The write path still assumes only Composer, a queue worker, outbound HTTPS, and a secrets store — unchanged, and cheap. The read path adds requirements we should name explicitly: a supervised long-running process (Horizon-style supervisor or a dedicated worker pool), durable cursor storage, and egress tolerant of sustained streaming connections rather than request/response. No VPC or allowlist work, but the DPoP key material expands the secrets-store scope beyond a simple app-password entry.
Migration path. Still greenfield, still a containment play — but now two containments, not one. Keep the publishing capability behind a first-party SocialPublisher contract with a BlueskyDriver implementation, as before. Treat the firehose/notification capability as a separate, later decision behind its own interface (a SocialEventListener-style contract), so that adopting the write path does not implicitly commit us to operating a stream consumer. The OAuth provider, if it works, is the one component worth leaning on directly rather than wrapping, since reimplementing DPoP is precisely the work we want to avoid.
Compatibility. Three things to verify in the spike rather than assume. First, the illuminate/support constraint against our Laravel version — a composer.json read, as before. Second, whether BlueskyProvider requires Socialite as a new dependency and whether the OAuth callback route and session/token storage fit our existing auth surface. Third, a live smoke test of the full write path — authenticate via OAuth and via app password if both are supported, post, upload media, delete — plus a time-boxed firehose smoke test that deliberately kills the consumer and confirms it resumes from the right cursor. Treat the passing smoke tests as the gate, not the README; a release that adds auth and streaming is a bigger claim than one that refines posting.
Sequencing. Reordered to separate the two capabilities and take the OAuth win early without taking the streaming bet:
BlueskyProvider and the app-password path, post / media / delete, observe errors and rate limits.FirehoseSyncMessage and activity subscriptions against a concrete product need. If approved, scope separately: consumer process, cursor store, backfill, subscription renewal.Maintenance. The headline cost rises. A single-maintainer package at 44 stars was already a fork candidate; it is now a fork candidate that owns auth cryptography and stream handling, both of which we cannot simply wait on. Budget moves from a few engineer-days per quarter to roughly a few engineer-days per quarter plus a standing assumption that we may need to patch the DPoP or sync path ourselves between upstream releases. Forking remains cheap because the codebase is small, but the blast radius of a stale fork is larger than it was — a drifted posting wrapper degrades publishing, whereas a drifted auth wrapper locks users out.
Support. Still no commercial support and no community to lean on. The 1.1.0 release partially answers our earlier question about maintainer responsiveness — shipping DPoP and exp claim fixes is evidence of active protocol tracking — but it also raises the bar: we now need a named owner competent in OAuth/DPoP, not just a Laravel generalist. AT Protocol incident escalation still routes to Bluesky's developer channels.
Scaling. The write path is unchanged: Bluesky's per-account and per-endpoint quotas bound throughput, publishing is low-volume and queue-shaped, and the design task is per-account concurrency control rather than raw scale. The read path is a different regime: a firehose consumer is bounded by stream volume and our own processing throughput, and it scales by sharding and by how much of the stream we actually need. Before committing, confirm whether FirehoseSyncMessage is a full-network stream (volume-heavy, filter aggressively) or a scoped subscription. The OAuth provider also meaningfully improves the multi-tenant scaling story — hundreds of accounts with per-user tokens and keys is now a design question rather than a fork-or-abandon question.
Failure modes. The original list stands — revoked app passwords causing blanket 401s, silent 429s becoming retry storms, duplicate posts from non-idempotent retries, partial threaded posts, untyped errors from lexicon changes. Three new ones matter and page different people: (a) DPoP proof defects producing intermittent, non-deterministic 401s that look like our bug, controlled by pinning a known-good version, canarying auth on a staging account, and alerting on auth-failure rate rather than individual failures; (b) firehose consumer disconnect without cursor resume producing silent data gaps, controlled by cursor-persistence tests that explicitly kill the process and a lag/gap metric with alerting; (c) lapsed activity subscriptions silently stopping notifications, controlled by a renewal check with its own alert. Each maps to a concrete control, as before — the difference is that (b) and (c) are only relevant if we take the read path, which is an argument for keeping that decision separate.
Ramp-up. Still low for the write path: any Laravel-fluent engineer is productive in under a day, and the OAuth provider arguably lowers ramp-up by removing bespoke auth work. The read path is a step up — long-running workers, cursor semantics, and reconnect behavior are operational skills rather than Laravel idioms, and the DPoP work means someone on the team should actually understand proof-of-possession rather than treating it as a black box. Document AT Protocol session semantics, DPoP key handling, app-password scoping, and rate-limit policy once; the ongoing cognitive load then drops near zero for the write path.
TPM recommendation. Proceed with the write-path spike as previously scoped, and treat 1.1.0 as net-positive for that work: the OAuth provider removes a blocker we had flagged and makes multi-tenant publishing worth validating early. Do not extend the same commit to the firehose or notification-subscription features — those add a stream consumer we do not operate today and should clear their own decision gate against a concrete product need. The maturity concern is unchanged in kind but larger in consequence, so gate production on: a passing OAuth + app-password + post/media/delete smoke test against our Laravel version, a written answer on DPoP key storage and rotation, and a named owner for the fork if upstream goes quiet.
How can I help you explore Laravel packages today?