NAME

Discord::Client::HTTPInteractions - Signed HTTP Interactions and PSGI delivery

SYNOPSIS

use Discord::Client::HTTPInteractions;

# Configure signature verification and command dispatch.
my $service = Discord::Client::HTTPInteractions->new(
    public_key => $public_key, loop => $loop,
);
$service->command(
    ping => (
        description => 'Replies with Pong',
        handler => sub {
            my ($interaction) = @_;
            return $interaction->reply('Pong!');
        },
    ),
);

# Let the host await this Future on the supplied loop.
my $response = $service->handle(\%headers, $raw_body);

DESCRIPTION

Receives commands and components over HTTPS without a Gateway connection. Handlers use Discord::Client::Interaction; initial replies are returned inline. Inline replies need no bot token; followups use the interaction token through REST. Command deployment needs a separate Bot-authorized client; see "sync" in Discord::Client::Commands.

METHODS

new

new(public_key => $key, loop => $loop) returns a receiver. public_key is the required 64-character hexadecimal application public key. loop is optional; otherwise one is created. rest and commands can inject caller-owned components. application_id sets the registry's application ID.

max_body defaults to 1048576 bytes, max_pending to 512 active handlers, and max_replay to 1024 completed response entries. max_seen defaults to 10,000 deduplication IDs, independent of response storage. replay_ttl defaults to 900 seconds and must be a positive integer. Running handlers remain protected until they finish, even beyond this period. max_age defaults to 300 seconds of timestamp difference; zero disables this SDK freshness policy. Keep the host clock synchronized. Tests can inject wall_clock, clock and delay. on_error receives ($error, $interaction) synchronously.

command

command(name, declaration_pairs) registers a command and returns the service. Arguments follow "define" in Discord::Client::Commands; it does not deploy.

component

component(custom_id => CODE) registers a handler and returns the service.

handle

handle returns a Future yielding [status, [headers], [body_bytes]]. Pass exact raw body octets before decoding or parsing. Header names are case-insensitive. Verification covers the timestamp followed by those octets.

Missing or invalid signature

HTTP 401.

Body over max_body

HTTP 413.

Invalid body or JSON

HTTP 400.

Duplicate whose response was evicted

HTTP 409, without repeating the handler.

Unavailable service or capacity

HTTP 503.

Commands do not run for rejected requests. Diagnostics omit request bodies and signatures. Handlers use the same Interaction API as Gateway delivery; initial acknowledgements are returned inline.

psgi_app

Returns a PSGI application coderef. Host requirements are described under "PSGI AND ASYNCHRONOUS HANDLERS".

loop

Returns the supplied or created IO::Async::Loop.

rest

Returns the REST client used for followups.

commands

Returns the Commands registry.

dispatcher

Returns the Interactions dispatcher.

stats

Returns request, signature, duplicate and error counters as a hashref, plus pending_requests, replay_entries and seen_entries.

close

Returns a Future for cleanup. Pending HTTP responses complete with 503; handlers and timers are cancelled, and owned resources are closed. Injected REST and Commands objects remain caller-owned. Call it when the host stops.

PSGI AND ASYNCHRONOUS HANDLERS

psgi_app returns a coderef accepting POST requests. It reads psgi.input using Content-Length and returns an immediate response or delayed responder. The initial response deadline is three seconds, including body reading and verification.

Immediate handlers work on an ordinary PSGI host. Asynchronous handlers and work after defer need a host that advertises psgi.streaming and drives the supplied IO::Async::Loop. Streaming alone does not run pending Futures. The adapter does not run another loop. Without delayed-response support, a pending initial response is cancelled and returns 503.

DUPLICATES

Signed duplicates of running handlers share the initial response. Completed responses are copied for replay until replay_ttl expires or max_replay requires eviction of the oldest completed response. Evicted responses return 409 for duplicate IDs; their handlers do not run again while the IDs are retained.

max_seen bounds the retained IDs; reaching it rejects new requests with 503. Entries expire on subsequent requests, after their handlers finish. A dispatcher capacity rejection before a handler starts is not retained, so a retry can run when capacity returns. Failures after dispatch remain protected because the handler may have performed side effects. This is process-local. Use shared deduplication across workers if your side effects require it.

SERVE THE EXAMPLE

Set DISCORD_PUBLIC_KEY to the application's 64-character hexadecimal public key from the Developer Portal. Install Plack and a PSGI server, then run:

PERL5LIB=lib plackup examples/http-interactions.psgi

Expose the service over public HTTPS and set its Interactions Endpoint URL in the Developer Portal. plackup alone serves local HTTP. The SDK verifies signatures and answers signed PING requests. HTTP and Gateway are alternative interaction delivery methods for the application.

LICENSE

Apache License 2.0.