Pular para o conteúdo principal

Sports

Integracao de sportsbook multi-provider no template. Os providers suportados sao First (iframe), Altenar (JS SDK), Betby (JS SDK) e Rogue (sportsbook nativo, SDK React) — a uniao vive no type SportsProvider de @cactus-agents/sports:

const PROVIDERS: readonly SportsProvider[] = ["first", "altenar", "betby", "rogue"];

:::info @cactus-agents/sports-rogue nao vive no core O SDK do Rogue e a dependencia @cactus-agents/sports-rogue (ver a versao em front-web-base/package.json). Ela usa o scope npm @cactus-agents/* mas nao e publicada a partir de front-cactus-core — nao procure um packages/sports-rogue la. @cactus-agents/sports (esse sim do core) segue sendo o pacote de tipos/config/roteamento de provider. :::

Visao geral

app/config/sports/sports.ts (dados estaticos, brand-overridable)
|
+-- app/config/sports/provider.ts (override por AMBIENTE: FORCE_SPORTBOOK)
|
+---------+-----------+
| |
v v
useSportsConfig() sportsConfig (import direto) <-- client / server
| |
v v
_layout.tsx loaders / API routes
|
v
SportsIframeFirst / SportsAltenar / SportsBetby / SportsRogue

A selecao do provider e feita 100% por configuracao — sem mudancas de codigo para trocar entre providers.

:::info Regra arquitetural app/config contem somente dados estaticos de brand. Hooks, parsers e logica de negocio nao ficam em app/config. O hook useSportsConfig() vive em app/hooks/useSportsConfig.ts.

Excecao deliberada: app/config/sports/provider.ts guarda helpers puros (resolveForceSportbook, applyForceSportbook) que nao leem env — quem le e o caller (client via useClientEnv, server via context.cloudflare.env). :::

Configuracao

app/config/sports/sports.ts

Arquivo principal (brand-overridable). Import: ~/config/sports/sports — sem suffix .config.

import type { SportsModuleConfig } from "@cactus-agents/sports";

export const sportsConfig: SportsModuleConfig = {
main: null, // provider principal (/sports) — null = desabilitado
test: null, // provider de teste (/sports-test) — null = desabilitado

first: {
// Default: o First assume a UX mobile — bottom nav e footer somem do
// lado do site e o footer e encaminhado pro iframe via postMessage.
hideBottomNav: true,
hideFooter: true,
},

altenar: {
integration: "brandName",
libraryUrl: "https://sb2wsdk-altenar2-stage.biahosted.com/altenarWSDK.js",
},

betby: {
brandId: "00000000000000000000",
theme: "default",
libraryUrl: "https://ui.invisiblesport.com/bt-renderer.min.js",
},

rogue: {
theme: "default",
scheme: "dark",
oddsStyle: "decimal",
},
};

O sportsConfig e importado diretamente tanto no client quanto no server — nao existe um wrapper sports.server. As API routes fazem import { sportsConfig } from "~/config/sports/sports".

Override por brand

overrides/<brand-key>/app/config/sports/sports.ts

<brand-key> vem de ORIGIN_DOMAIN normalizado. Como todo override, a substituicao e do arquivo inteiro (sem deep-merge) — ver Override Files.

Hoje todas as brands do base declaram main: "first".

Override por ambiente — FORCE_SPORTBOOK

app/config/sports/provider.ts implementa um override em runtime, por ambiente, com prioridade sobre o main do arquivo:

export function resolveForceSportbook(raw?: string | null): SportsProvider | null;
export function applyForceSportbook(
config: SportsModuleConfig,
forced: SportsProvider | null,
): SportsModuleConfig;
  • A env FORCE_SPORTBOOK recebe o nome do provider ("first" | "altenar" | "betby" | "rogue").
  • Valor vazio, ausente ou invalido → null (usa o arquivo).
  • So o main e afetado; o test fica inalterado.

Pra que serve: rodar um ambiente dedicado (ex. sports-7k-bet-br…) com um provider diferente do de producao, na mesma brand / mesmo ORIGIN_DOMAIN, sem tocar no override. E o caminho usado hoje pra exercitar o Rogue: prod da 7k segue em First, e o ambiente dedicado seta FORCE_SPORTBOOK=rogue. Em dev local, FORCE_SPORTBOOK=rogue no .dev.vars tem o mesmo efeito.

SPORTS_ROGUE_API_KEY

Chave enviada como header x-api-key nas rotas de token do sportbook (/cactus-sportbook/auth/*) do BFF. Server-side only — nunca exposta ao client, setada como secret do worker (nao em [vars]). Necessaria quando o provider ativo e o Rogue.

Hook — useSportsConfig()

Hook central consumido por rotas e componentes (app/hooks/useSportsConfig.ts). Ele importa sportsConfig de ~/config/sports/sports, aplica o FORCE_SPORTBOOK e resolve o provider ativo via getActiveProvider() do SDK — entao todos os consumidores veem a config efetiva.

import { useSportsConfig } from "~/hooks/useSportsConfig";

const {
config, // SportsModuleConfig EFETIVO (com FORCE_SPORTBOOK aplicado)
main, // provider principal efetivo
test, // provider de teste
activeProvider, // provider ativo para a rota atual
hasSports, // true se main != null
hasTestRoute, // true se test !== null
isTestRoute, // true na rota de teste
getConfigFor, // sub-config de um provider
firstHideBottomNav, // flags do First com default seguro (undefined === true)
firstHideFooter,
} = useSportsConfig();

Componentes

SportsIframeFirst

Renderiza <iframe> do First com comunicacao postMessage. Sincroniza navegacao bidirecional entre o iframe e o React Router via URLs semanticas.

SportsAltenar

Carrega o SDK Altenar dinamicamente, inicializa com AltenarWSDK.init() e sincroniza navegacao via onRouteChangeDetailed. Busca token de auth em /api/sports/altenar-token.

SportsBetby

Carrega o SDK Betby (BTRenderer) e autentica via /api/sports/betby-jwt. Recarrega a pagina em mudancas de auth (login/logout) para reinicializar o SDK.

SportsRogue

Monta o SDK React nativo (@cactus-agents/sports-rogue) via createSportsbook, passando callbacks de auth/login/deposito, locale, tema, etc. O SDK renderiza o sportsbook in-app (Shadow DOM) consumindo a Rogue API da First. Fica em app/components/sports/SportsRogue.tsx.

Campos host/env-derivados (locale, currency, logo do ticket, logoBaseUrl/assetBaseUrl) nao ficam na config da brand — o SportsRogue.tsx os resolve do brand/env.

Rotas

ChavePath padraoDescricao
sports/sportsLayout + index. Renderiza o provider ativo.
sports.catchAll/sports/*Catch-all. Deep links de esporte.
sports.test/sports-testProvider alternativo para testes.
search.sports/search/sportsBusca esportiva (pagina propria)

Paths sao configuraveis via Route Registry (~/config/routes/paths); use routeHref() / sportPath() de ~/utils/routes.

O layout (routes/sports/_layout.tsx) monta o componente do provider uma vez e mantem vivo em todas as sub-rotas. O conteudo interno e controlado pelo SDK do provider, nao por rotas React.

API Routes

RotaDescricao
api/sports/searchBusca esportiva
api/sports/launchLaunch First (autenticado)
api/sports/anonymous-launchLaunch First (anonimo)
api/sports/betby-jwtJWT para Betby
api/sports/altenar-tokenToken para Altenar
api/sports/rogue-anonymousToken anonimo da Rogue API
api/sports/rogue-loginToken logado da Rogue API (cookie de sessao)
api/sports/rogue-proxy/*Proxy same-origin para a Rogue API real, com streaming de SSE

Define os items de navegacao com paths por provider. Import: ~/config/layout/sidebar-sports.

Exports:

ExportConteudo
sportsMainItemsItems da secao "Esportes" (hoje 3: live, upcoming, e-sports)
topSportsItemsSub-secao "Top Sports" (hoje 5 esportes)
popularChampionshipsSecao "Popular" (hoje 12 campeonatos)
resolveMenuItemPathRe-export de @cactus-agents/sports
SportsSidebarItemType — estende SportsSidebarItemBase com icon, i18nKey, borderGradient
export const sportsMainItems: SportsSidebarItem[] = [
{
slug: "live",
i18nKey: "layout:sidebar.live_sports",
label: "Live",
section: "main",
firstPath: sportPath("/live"),
betbyPath: sportPath("/live"),
altenarPath: sportPath("/live"),
roguePath: sportPath("/"),
},
// ...
];

Cada item carrega firstPath, betbyPath, altenarPath e/ou roguePath; o path correto e resolvido em runtime por resolveMenuItemPath().

:::note roguePath cai na home do sportsbook O esquema de URL do SDK Rogue enderece apenas sport/league/event por slug — nao ha URL de filtro "live"/"upcoming" nem um esporte unico "E-Sports". Por isso os sportsMainItems apontam pro root do sportsbook (sportPath("/")), que ja abre com o filtro live. Deep-links por slug pra Top Sports entram nos overrides por brand, porque os slugs sao locale-specific. :::

A lista e brand-overridable (overrides/<brand>/app/config/layout/sidebar-sports.ts), e hoje a maioria das brands do base tem o proprio.

Configurando uma nova marca

  1. Criar overrides/<brand-key>/app/config/sports/sports.ts
  2. Definir main com "first", "altenar", "betby", "rogue" ou null
  3. Opcionalmente definir test para habilitar /sports-test
  4. Configurar o bloco do provider escolhido no mesmo arquivo. Para rogue, o override da 7k-bet-br traz o bloco completo e comentado (paleta + priorizacao de esportes/regioes/ligas/mercados) — use como referencia
  5. Se o ambiente precisa de um provider diferente do main estatico, use FORCE_SPORTBOOK em vez de criar um override novo
  6. Customizar items do sidebar em overrides/<brand-key>/app/config/layout/sidebar-sports.ts se necessario