Skip to content

Twitch: Saloon Transport + OAuth Migration + Legacy Cleanup #267

@danielhe4rt

Description

@danielhe4rt

Parent

#266 — Twitch EventSub: ingestão de eventos via webhook (data lake)

What to build

Migrate all outbound HTTP in the integration-twitch module from raw Guzzle to SaloonPHP, matching the pattern established in integration-discord. This is the foundational slice — all other Twitch EventSub work depends on it.

End-to-end behavior

After this slice, a user can log in via Twitch (OAuth flow works through Saloon), and the system can obtain and cache an App Access Token via client credentials for server-to-server API calls. All legacy Guzzle code is deleted.

What changes

New — Saloon Connectors:

  • TwitchOAuthConnector — base URL id.twitch.tv/oauth2, no default auth
  • TwitchHelixConnector — base URL api.twitch.tv/helix, default auth with App Access Token + Client-Id header. Token received via constructor, resolved by ServiceProvider.

New — Saloon Requests (organized by Helix API resource):

  • Requests/OAuth/ExchangeCodeForToken — POST /token, form body with auth code
  • Requests/OAuth/GetAppAccessToken — POST /token, form body with client_credentials
  • Requests/Users/GetCurrentUser — GET /users, user token override
  • Requests/Users/GetUsers — GET /users?login=, app token default
  • Requests/EventSub/CreateSubscription — POST /eventsub/subscriptions, JSON body
  • Requests/EventSub/ListSubscriptions — GET /eventsub/subscriptions
  • Requests/EventSub/DeleteSubscription — DELETE /eventsub/subscriptions?id=

New — App Token Service:

  • TwitchAppTokenService (in OAuth/) — uses TwitchOAuthConnector to fetch client_credentials token, caches with Cache::remember(), TTL = expires_in minus 300s buffer

Refactored — OAuth Client:

  • TwitchOAuthClient — still implements OAuthClientContract, but internally uses TwitchOAuthConnector and TwitchHelixConnector instead of raw Guzzle

Updated — Identity module:

  • IdentityProvider::Twitch in the enum — changed from resolve(TwitchOAuthService::class) to resolve(TwitchOAuthClient::class) (same pattern as Discord)

Updated — Config:

  • config/services.php twitch array — add eventsub_secret and eventsub_callback env keys

Updated — ServiceProvider:

  • New singleton bindings for TwitchAppTokenService, TwitchOAuthConnector, TwitchHelixConnector
  • Remove old TwitchService and TwitchOAuthService bindings

Deleted:

  • Client/TwitchBaseClient.php (facade)
  • Contracts/TwitchService.php (facade interface)
  • OAuth/Contracts/TwitchOAuthService.php (intermediate interface)
  • Subscriber/ directory (entire legacy subscriber module)

Acceptance criteria

  • TwitchOAuthConnector and TwitchHelixConnector exist and follow the Saloon connector pattern from integration-discord
  • All 7 Saloon Request classes exist with correct endpoints, methods, and body/query handling
  • TwitchAppTokenService fetches and caches app access token via client_credentials flow
  • TwitchOAuthClient uses Saloon internally; redirectUrl(), auth(), getAuthenticatedUser() work correctly
  • IdentityProvider::Twitch->getClient() resolves TwitchOAuthClient::class directly
  • config/services.php has eventsub_secret and eventsub_callback keys in the twitch array
  • Legacy files deleted: Client/TwitchBaseClient.php, Contracts/TwitchService.php, OAuth/Contracts/TwitchOAuthService.php, entire Subscriber/ directory
  • No Guzzle usage remains in the module (all HTTP via Saloon)
  • ServiceProvider registers new bindings, removes old ones
  • Tests: Saloon MockClient tests for each connector and request
  • Tests: TwitchAppTokenService caching behavior (fetch, cache hit, TTL)
  • Tests: TwitchOAuthClient refactored flow (auth, getAuthenticatedUser)
  • vendor/bin/pint --dirty --format agent passes
  • php artisan test --compact --filter=Twitch passes

Blocked by

None — can start immediately.

Metadata

Metadata

Assignees

No one assigned

    Labels

    ready-for-agentFully specified, ready for an AFK agent

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions