Skip to content

struct Athena::Mercure::Bundle
inherits Athena::DependencyInjection::AbstractBundle #

Integrates the Mercure component with the dependency injection and HTTP kernel components, such as within an Athena Framework application.

Require athena-mercure/bundle to register the bundle, then configure at least one hub:

require "athena"
require "athena-mercure/bundle"

ADI.configure({
  mercure: {
    hubs: {
      default: {
        url: ENV["MERCURE_URL"],
        jwt: {
          secret:    ENV["MERCURE_JWT_SECRET"],
          publish:   ["*"],
          subscribe: ["*"],
        },
      },
    },
  },
})

See Schema for the full set of possible configuration options.

If multiple hubs are configured, they may be injected using the hub name as a constructor parameter typed to AMC::Hub::Interface. For example some_hub : AMC::Hub::Interface assuming the key used in the hubs named tuple was some_hub.

The bundle brings AMC::Hub, AMC::Bundle::Authorization, and AMC::Bundle::Discovery into the framework as injectable services, with event listeners that handle response headers and cookies automatically.

Publishing#

Inject AMC::Hub::Interface to publish updates from a controller:

@[ARTA::Route(path: "/broadcast")]
class BroadcastController < ATH::Controller
  def initialize(@hub : AMC::Hub::Interface); end

  @[ARTA::Post("/")]
  def broadcast : Nil
    @hub.publish AMC::Update.new(
      "https://example.com/books/1",
      {status: "OutOfStock"}.to_json
    )
  end
end

Authorization#

Inject AMC::Bundle::Authorization to set the mercureAuthorization cookie for private updates. The AMC::Bundle::Listeners::SetCookie listener automatically adds the cookie to the response — there is no need to modify the response directly:

@[ARTA::Route(path: "/auth")]
class AuthController < ATH::Controller
  def initialize(@authorization : AMC::Bundle::Authorization); end

  @[ARTA::Get("/subscribe")]
  def subscribe(request : AHTTP::Request) : Nil
    @authorization.set_cookie(
      request,
      subscribe: ["https://example.com/books/{id}"],
    )
  end
end

See Authorization in the Mercure component docs for more on private updates and cookie-based auth.

Discovery#

Inject AMC::Bundle::Discovery to add the Mercure hub Link header to a response. The AMC::Bundle::Listeners::AddLinkHeader listener handles adding the header — there is no need to modify the response directly:

@[ARTA::Route(path: "/books")]
class BookController < ATH::Controller
  def initialize(@discovery : AMC::Bundle::Discovery); end

  @[ARTA::Get("/{id}")]
  def show(request : AHTTP::Request, id : Int32) : {id: Int32, title: String}
    @discovery.add_link request

    {id: id, title: "Hello World!"}
  end
end

The response will include a Link: <https://hub.example.com/.well-known/mercure>; rel="mercure" header. A client can then extract the hub URL to subscribe to updates for this resource:

fetch('/books/1')
  .then(response => {
    const hubUrl = response.headers.get('Link').match(/<([^>]+)>;\s+rel=(?:mercure|"[^"]*mercure[^"]*")/)[1];

    const hub = new URL(hubUrl, window.origin);
    hub.searchParams.append('topic', 'https://example.com/books/{id}');

    const eventSource = new EventSource(hub);
    eventSource.onmessage = event => console.log(event.data);
  });

See Discovery in the Mercure component docs for more on the discovery protocol.