OFICINA · GAMERS PELO BRASIL
Cópia pública do guia canônico. SDK de testes com API publicada. Chave e origens são fornecidas pelo operador; login Google depende da configuração do ambiente.
Edição pública gerada de backend/sdk/README.md. Revisão oficina-2026-10-23.3; SDK 0.1.0-test.3. Links internos não distribuídos são identificados.
SDK de troféus — versão de testes
Decisão de Murillo em 2026-10-17: o backend passa a centralizar conquistas
de todos os jogos. Novas integrações enviam apenas conquistas, sem score ou diário
de partida. Cada jogo dispõe de bronze-1, bronze-2, bronze-3, prata-1,
prata-2, ouro-1 e diamante-1. São sete conquistas distintas por jogo, cada
uma guardada uma vez por jogador; não são sete premiações globais para dividir
entre jogadores. Os critérios de conquista são responsabilidade do jogo.
Fluxo implementado
- Murillo cadastra o jogo e suas origens com a CLI administrativa e entrega a chave pública ao criador. O jogo instala o módulo JavaScript.
- Ao ocorrer a conquista,
ganhar(slot)cria um token aleatório de 256 bits e um UUID v4. Envia somente UUID, slot e SHA-256 do token à API com a chave. Nenhum jogador, score, cookie ou evento intermediário sai do jogo. - A API valida jogo, origem, chave, slot e reenvios, guarda o hash no PostgreSQL
e informa o prazo e a página central. O SDK monta o link com o token no
fragmento:
/trofeus/#token=.... O token não vai na query nem no GET. - O jogador abre o link. A página central retira o fragmento do endereço e
mantém uma fila em
sessionStorage, inclusive durante reload. Cada resposta confirma apenas o token enviado; links que chegam durante o POST permanecem na fila. O GET não concede troféu. A confirmação envia o token em POST à API, com credentials include e CORS restrito ao hub. - O backend reconhece a identidade pelo cookie HttpOnly existente, confere o hash e grava a conquista em transação. Link consumido por outro jogador é recusado. A coleção reúne os jogos, sem ranking por score.
O servidor não lê storage/cache do navegador. localStorage pertence à origem
que o criou; cookies são enviados pelo navegador conforme domínio e atributos.
Referências técnicas consultadas em 2026-10-17:
cookies e
localStorage.
Desde a publicação de 2026-10-21, a central pertence a HUB_ORIGIN, no hub.
O endereço antigo PUBLIC_ORIGIN/trofeus/ permanece como redirecionamento 302
sem HTML ou fragmento próprio, preservando links e SDKs existentes. O campo
claim_url da API v1 permanece inalterado por compatibilidade. O cookie continua
host-only na API; o hub usa credentials include. A fila antiga da origem da API
não é copiada: reabra o link no jogo ou use renovarLink se venceu. Troféus já
persistidos continuam na coleção.
Instalar e usar no jogo
Pacote ESM sem dependências: @gamers-pelo-brasil/trofeus, versão
0.1.0-test.3. Não publicado no npm. Instalação pela pasta do código-fonte (com package.json), para quem tem acesso:
npm install /caminho/para/backend/sdk
O ZIP público usa arquivos diretos e não requer npm. Para HTML sem bundler, comece pelo exemplo completo e copiável.
Copie exemplo.html e trofeus.js para a mesma pasta, sirva por HTTPS ou
localhost e preencha a API/chave entregues pelo operador. Não abra por file://.
Em produção, use api: 'https://api.gamerspelobrasil.tech', sem barra final nem
/api/v1. O módulo publicado fica em
https://gamerspelobrasil.tech/criar/sdk/arquivos/trofeus.js; o caminho antigo
https://api.gamerspelobrasil.tech/sdk/trofeus.js redireciona para ele.
Prefira copiar o módulo do ZIP para o próprio jogo, como no exemplo.
A central de resgate fica no hub.
O link retornado pelo SDK passa pela API e redireciona para a central;
use receipt.claim_url inteiro, sem remover nem enviar o fragmento ao servidor.
Use somente API, chave e origens fornecidas pelo operador. Login Google depende
da configuração do ambiente; o SDK não configura nem solicita credenciais de conta.
- Peça ao operador a origem da API, a chave pública, as origens cadastradas do jogo e a correspondência entre desafios e os sete slots. O criador não precisa acessar banco, CLI ou credenciais administrativas.
- Prepare o SDK antes de iniciar a partida. No exemplo, “Testar conexão” só diagnostica; “Emitir conquista real”
representa um callback real e grava uma prova. Ligue cada callback a
registrarConquista(slot):
js
// Dentro do script do exemplo, depois de preparar o SDK:
aoVencerPrimeiraFase(() => registrarConquista('bronze-1'));
aoCompletarJogo(() => registrarConquista('ouro-1'));
Substitua aoVencerPrimeiraFase/aoCompletarJogo pelos eventos existentes no
seu jogo; não crie regras de vitória novas. Cada slot tem sua própria linha,
status, link Receber na minha coleção, Tentar novamente e Gerar novo link.
3. Falhou a rede? Use Tentar novamente. A conquista fica registrada antes do
envio, mesmo se o callback ocorrer uma única vez. Após reload, prepare o SDK
com a mesma API/chave: as linhas são restauradas sem precisar vencer de novo.
4. A central disse CLAIM_USED ou link vencido? Dispense o link antigo na central
se for reutilizar a mesma aba. Volte à linha da conquista e use
Gerar novo link. Esse ato explícito substitui a prova local daquele slot.
Se a resposta da renovação se perder, use Tentar novamente, que reutiliza
a prova recém-criada. Não clique de novo em renovar para repetir transporte.
O novo link permite resgatar na identidade atual; não recupera a coleção antiga.
5. Teste dois slots, interrompa uma resposta e recarregue. Cada linha deve manter
seu próprio botão; o retry não depende de reiniciar a partida. Abra receber,
entre com Google se a central solicitar e confirme o resgate. Em ambientes
com Google desativado, a central oferece uma identidade de teste.
Para integração assistida por IA: forneça o HTML completo e o mapeamento de slots;
peça para ligar apenas os callbacks reais a registrarConquista, preservando as
linhas/retries, API/chave pública fornecidas e credentials: 'omit'. Cadastro,
banco e configuração do proxy são do operador. Não peça login no jogo ou mudanças
no sandbox/CORS do conteúdo hospedado.
Chave, origem e configuração no jogo
A chave gpb_… é pública e pode ficar no JavaScript. Use a chave do próprio jogo;
ela identifica o cadastro, não autentica o jogador e não comprova a vitória.
Não peça a chave ao jogador. Na versão integrada, preencha api e chave no
código e prepare o SDK ao carregar o jogo, antes de habilitar a partida.
O formulário do exemplo serve para o autor testar a configuração.
import {criarTrofeus} from './trofeus.js';
const sdk = criarTrofeus({
api: 'https://api.gamerspelobrasil.tech',
chave: 'CHAVE_PUBLICA_ENTREGUE_PELO_OPERADOR'
});
// Mantenha as linhas, os botões e a recuperação do exemplo completo.
// Cada callback real chama registrarConquista(slot), que usa esta instância.
A origem é protocolo + domínio + porta, sem caminho nem barra final.
https://meu-jogo.pages.dev é diferente de um subdomínio de preview,
http://localhost:8091 ou http://127.0.0.1:8091. Só funcionam as origens
cadastradas para aquela chave. Servir por localhost habilita Web Crypto, mas
não libera a emissão real: para testar sem gravar, use testar(slot) abaixo;
para emitir conquistas locais de verdade, solicite ao operador o cadastro exato.
Publicar o exemplo na origem autorizada também permite testar a chave real.
Não use mode: 'no-cors', não defina Origin manualmente e preserve
credentials: 'omit'; o SDK cuida dos cabeçalhos e do preflight.
Se o jogo tiver Content-Security-Policy, permita a API em connect-src e o
local do módulo em script-src. Isso vale para o frontend externo administrado
pelo autor; não altera o isolamento dos jogos hospedados pela plataforma.
| Sintoma | Conferência e ação |
|---|---|
Chave do jogo inválida ao preparar |
Substitua o placeholder pela chave completa gpb_ + 64 hexadecimais. |
ORIGIN_NOT_ALLOWED / erro de CORS ou Failed to fetch |
Veja no Network o OPTIONS/POST: confira origem cadastrada, HTTPS, CSP e conectividade. O navegador pode ocultar a resposta quando CORS bloqueia. |
INVALID_GAME_KEY (403) |
Confira se a chave está ativa e pertence ao jogo e à origem usados. |
RATE_LIMITED (429) |
Aguarde um minuto e tente novamente com a mesma prova. |
CLAIM_EXPIRED / CLAIM_USED |
Gere novo link por ação explícita no jogo; retry sozinho preserva o link antigo. |
Testar em localhost sem gravar conquistas
A partir do SDK 0.1.0-test.3, use a mesma chave pública do jogo para
verificar conexão, chave ativa e slot, mesmo sem cadastrar localhost:
const resultado = await sdk.testar('bronze-1');
// {mode: 'test', game_id: 'meu-jogo', trophy_id: 'bronze-1',
// origin_registered: false, persisted: false}
Copie exemplo.html e trofeus.js do ZIP para a mesma pasta e sirva:
python3 -m http.server 5173 --bind 127.0.0.1
Abra http://localhost:5173/exemplo.html, preencha a API de produção e a chave,
clique Preparar SDK e Testar conexão (sem gravar). Não é necessário banco,
CLI, conta ou uma chave de teste separada. O operador já deve ter cadastrado a
chave do jogo; chave inventada, girada ou desativada retorna erro.
O diagnóstico faz POST /api/v1/trophies/check com X-Game-Key e corpo
{"trophy_id":"bronze-1"}. O servidor executa apenas leitura: não cria claim,
token, sessão, troféu nem debita cotas SQL de emissão. O SDK não modifica as
intenções em storage e não devolve link de resgate. origin_registered: false
é esperado em localhost sem cadastro; persisted é sempre false.
As defesas gerais da API continuam limitando excesso de requisições.
Aceita http://localhost:<porta>, http://127.0.0.1:<porta> e
http://[::1]:<porta> (ou HTTPS), com porta válida. LAN, file:///Origin null
e previews não cadastrados continuam recusados. A exceção local vale somente
para /check; ganhar(slot) e Emitir conquista real continuam gravando
provas e exigindo origem cadastrada. Não use emissão como teste de conexão.
Se /check responder 404, a API ainda não recebeu a versão com diagnóstico;
se testar não existir, atualize o módulo. Não substitua o diagnóstico por emissão.
Critérios de integração concluída
Mapeie os desafios existentes para os slots que o jogo usar; não é obrigatório atribuir os sete de uma vez. Um link pronto significa que a prova foi emitida. O troféu só entra na coleção após o jogador confirmar o resgate na central. Valide um callback natural, dois slots independentes, falha de rede/retry, reload, renovação explícita e resgate/coleção com a conta do jogador. Instalar o módulo e obter HTTP 200 não substitui esse fluxo completo.
Roteiro de validação com o criador
O retorno do criador é parte do piloto: confirme o mapeamento de eventos existentes para slots antes do aceite. Os critérios pertencem ao jogo; não há exigência de dificuldade uniforme entre jogos. Cada slot é independente por jogo, mesmo quando dois jogos usam o mesmo identificador.
Use estas três etapas e declare qual foi concluída:
| Etapa | Como executar | O que comprova |
|---|---|---|
| Diagnóstico sem gravação | testar(slot) com a chave pública |
Conexão, chave ativa, slot e se a origem está cadastrada. Não cria prova ou link. |
| Simulação no jogo | Acionar os eventos existentes com envio interceptado no ambiente de teste do autor | Callbacks, apresentação das conquistas e tratamento de falhas simuladas. Não comprova emissão ou coleção na central. |
| Aceite real | Jogar na origem cadastrada, emitir pelo SDK, abrir o link e confirmar na central com a conta | Integração do jogo com emissão, login quando solicitado, resgate e coleção. A persistência deve ser conferida com o operador. |
A simulação é útil e não impede o aceite real coordenado com o operador. Uma falha de rede provocada que apresenta Tentar novamente é um resultado esperado; para validar recuperação, é preciso também concluir o reenvio. Não use emissão para diagnosticar conexão nem execute emissões em massa na API pública. Faça os cenários de falha técnica em ambiente controlado.
Antes de concluir a integração, confira:
- Eventos naturais: cada slot dispara no momento definido pelo jogo. Teste também os casos que não devem premiar, o reinício da partida e, quando houver, a diferença entre chegar a uma rodada, concluí-la e terminar sem erros. Em celular, exercite os controles de toque usados para conquistar o troféu.
- Recuperação: use dois slots; interrompa a resposta de uma emissão já aceita pela API, recarregue e prepare a mesma API/chave. Use Tentar novamente sem repetir a vitória: a prova deve ser a mesma, sem duplicação, com linhas e links independentes. Ver a mensagem de falha, sozinha, não comprova essa recuperação.
- Renovação: para link vencido ou usado, gere novo link por ação explícita. Se a resposta da renovação se perder, tente novamente sem renovar outra vez. Nenhuma dessas ações deve apagar o link ou a intenção de outro slot.
- Conta e coleção: em cada jogo, conquiste naturalmente no celular e siga Receber na minha coleção → central → Google, se solicitado → confirmação. Confira que o login preserva a pendência nesta aba e que o troféu permanece após reabrir a coleção e entrar com a mesma conta em outro navegador.
O criador registra versão/URL do jogo, slot e evento, data/hora, aparelho, navegador, etapa testada e resultado observado. Não inclua tokens, cookies ou links pessoais. O operador confere a persistência independentemente; o criador não precisa de banco ou credenciais administrativas. Uma partida automática não substitui a conferência em aparelho físico; um ensaio sintético da equipe não substitui o callback natural do jogo.
Nomes das conquistas no jogo e na coleção
Você pode apresentar títulos e descrições próprios no jogo, associados aos slots.
Na versão atual, a central exibe a categoria do troféu e o nome cadastrado do
jogo; não recebe o título, a descrição ou o ícone personalizado da conquista.
Por exemplo, “Escavador” associado a bronze-1 continua sendo o bronze daquele
jogo na coleção. Envie somente os campos do contrato pelo SDK.
A personalização da central é uma evolução opcional e não bloqueia o piloto.
API JavaScript e recuperação
ganhar(slot) registra a intenção e envia/reenvia a mesma prova; chamadas
simultâneas do mesmo slot compartilham a promessa. pendentes() lista os slots
com intenção local, inclusive quando o envio falhou; não indica que houve resgate.
renovarLink(slot) cria outro UUID/token explicitamente, sem consultar a
identidade central. A renovação em paralelo a um envio do mesmo slot falha com
ISSUE_IN_PROGRESS; espere a operação e use retry se necessário.
O SDK não faz chamadas ao inicializar, polling ou login. A primeira conquista faz POST (e preflight quando necessário). Após resposta válida, reutiliza o link; falha de transporte preserva UUID/token. O prazo do servidor é 15 minutos, sem renovar em reenvio. Não há substituição automática por relógio local: se a prova venceu, a UI oferece renovação explícita. Timeout de rede: 10 segundos; sem retry automático. O cache usa API/chave/slot. Sem storage disponível, a intenção fica apenas na memória da página e não sobrevive a reload. Apagar storage, mudar origem ou chave também impede recuperar essa intenção; localStorage não é conta central.
Cadastrar, girar ou desativar a chave
Para experimentar manualmente, com DATABASE_URL de testes já definida, execute
na pasta backend/ (terminal 1):
make build
HTTP_ADDR=127.0.0.1:8082 PUBLIC_ORIGIN=http://127.0.0.1:8082 HUB_ORIGIN=http://127.0.0.1:8081 ./bin/api
No terminal 2, com a mesma DATABASE_URL:
./bin/trofeus -jogo piloto -nome 'Meu jogo piloto' -origens 'http://localhost:8091'
python3 -m http.server 8091 --bind 127.0.0.1 --directory sdk
No terminal 3, na pasta hub/, inicie a central local (com Go disponível):
HTTP_ADDR=127.0.0.1:8081 API_ORIGIN=http://127.0.0.1:8082 go run .
Abra http://localhost:8091/exemplo.html, cole a chave pública retornada e clique
em “Preparar SDK”, “Emitir conquista real” e “Receber na minha coleção”. O HTML
abre a central em outra aba e mantém os links de cada slot no jogo. A página
central oferece criar a identidade de teste somente se não houver sessão.
As duas origens diferentes reproduzem a separação jogo/central.
Use Ctrl+C para encerrar os processos de teste. Não é deploy nem publicação npm.
Depois de iniciar a API para aplicar migrations, a partir de backend/, use
DATABASE_URL de um banco de testes já configurado, sem imprimi-la:
make build
./bin/trofeus -jogo piloto -nome 'Meu jogo piloto' -origens 'http://localhost:8091'
./bin/trofeus -acao girar-chave -jogo piloto
./bin/trofeus -acao desativar -jogo piloto
A saída contém a chave pública (gpb_ + 64 hexadecimais), não a credencial
administrativa do banco. Só existe administração por CLI; nenhuma rota HTTP
pública cadastra jogos ou cria chaves. O banco guarda somente o hash da chave.
Cadastro duplicado falha sem substituir o jogo. Rotação invalida a chave anterior
e seus resgates pendentes; desativação bloqueia novas emissões e resgates. Ambas
preservam a coleção já conquistada. A CLI está incluída no Dockerfile, sem
executar mudanças no build.
Cadastre de uma a dez origens exatas, separadas por vírgula, sem caminho/barra
final; HTTPS obrigatório fora de loopback. Origem opaca null é recusada. Os
jogos hospedados atuais abrem diretamente em nova aba, com origem opaca por
cabeçalho CSP. Precisam de uma integração própria ainda não entregue; não se
remove o isolamento nem se aceita Origin null para instalar o SDK.
Contrato e fronteiras
Contrato canônico: OpenAPI, tag Troféus. Implementação: HTTP (referência interna; consulte o operador), transações (referência interna; consulte o operador) e migration 004 (referência interna; consulte o operador).
| Operação | Identidade/autorização | Efeito |
|---|---|---|
POST /api/v1/trophies/claims |
Chave pública e origem do jogo; sem sessão | Registra hash/UUID/slot e responde página e validade |
GET /trofeus/ na API |
Redirecionamento sem corpo | Leva à central do hub; não cria identidade nem grava conquista |
POST /api/v1/trophies/redeem |
Cookie e Origin central | Resgata uma vez; reenvio do dono retorna a mesma conquista |
GET /api/v1/players/me/trophies |
Cookie central | Até 1000 conquistas mais recentes, sem acesso entre jogadores |
A chave no JavaScript e o cabeçalho Origin são copiáveis. Esta versão valida a autorização do cadastro e a integridade do resgate, não prova que o desafio foi vencido. O token é um comprovante ao portador: quem possuir o link pode resgatá-lo primeiro. Não há identidade do jogador vinculada na emissão; esse é o compromisso do fluxo sem login nos jogos. Não publique links pessoais como compartilhamento. Para um futuro modo verificado, o servidor do jogo poderá atestar conquistas com credencial privada; isso não está implementado na versão de browser.
Limites por janela fixa de minuto, compartilhados no PostgreSQL entre processos:
4096 bytes por pedido; 10 provas novas por jogo/solicitante, 60 provas novas
por jogo (teto agregado preservado), 120 pedidos válidos por jogo/solicitante,
incluindo reenvios/conflitos e novas emissões recusadas. Pedidos sem autorização
ou malformados são recusados antes dessa admissão; proteção volumétrica desses
pedidos cabe à borda. Retry idempotente não debita provas novas, mesmo com o teto
agregado cheio; continua limitado pelos 120 pedidos. Falha transacional não deixa
claim nem débito de prova nova; a admissão do pedido persiste separadamente.
429 inclui Retry-After: 60. Resgate mantém 60 tentativas autenticadas/jogador/minuto.
Solicitante é o IP da conexão (IPv4 sem porta; IPv6 agrupado por /64), nunca um ID escolhido pelo browser. Só o hash desse agrupamento entra no contador. NAT compartilha a cota; uma rede distribuída ainda pode atingir o teto agregado. Isso reduz o abuso reproduzido e limita escrita, sem constituir proteção completa contra DDoS.
Por padrão, cabeçalhos de proxy são ignorados. Atrás de proxy, o operador deve
configurar TRUSTED_PROXY_CIDRS com CIDRs explícitos dos saltos controlados e
validar que a borda remove ou acrescenta o IP real à direita de X-Forwarded-For.
A API percorre a cadeia da direita para a esquerda até o primeiro IP não confiável;
prefixos inventados à esquerda não criam novas cotas. Não use redes amplas que
incluam clientes; cabeçalho ausente/inválido usa o IP do proxy e compartilha a cota.
A publicação de testes foi conferida em 2026-10-19; configuração e provas
operacionais pertencem ao ambiente (referência interna; consulte o operador).
Hash, UUID e corpo devem coincidir para reenvio. Claims não consumidos vencem em 15 minutos. Reenvio do dono após consumo continua idempotente mesmo depois desse prazo, enquanto jogo/chave e sessão forem válidos. Não há limpeza automática das provas nesta versão; política e operação ficam no quadro. Nenhuma concessão depende de memória do processo Go. Não recebe URL arbitrária para redirecionar nem busca URLs do jogo.
Identidade central: com Google habilitado pelo operador, o jogador entra
na central e recupera a coleção pela conta. O jogo não recebe essa sessão nem
solicita credenciais Google. Com Google desativado, a central oferece identidade
de teste por cookie defende_brasil_session, HttpOnly, SameSite=Lax, host-only e
Secure sob HTTPS. A duração de teste segue SESSION_TTL (padrão 24h, sem renovação).
Perder ou expirar o cookie de teste não recupera a coleção pela UI: uma nova
sessão cria outra identidade. Storage do jogo não é autenticação.
A configuração de conta não é fornecida no ZIP; confira o modo oferecido pela
central do ambiente disponibilizado. Não prometa migração de troféus de uma
identidade de teste para uma conta.
Reproduzir os ensaios
Com Go no PATH, PostgreSQL descartável e Playwright/Chromium configurados conforme
o ambiente (referência interna; consulte o operador), a partir de backend/:
make trophies-test
O alvo exige TEST_DATABASE_URL de cluster dedicado e PLAYWRIGHT_MODULE; não
aceita silenciosamente pular o navegador. Cada teste cria banco/role próprios e
remove-os no final. O navegador usa sandbox ativo e portas loopback; o teste
inicia/encerra apenas seus processos. O fixture externo serve o HTML completo e o SDK reais, com registro pela CLI e
API em processo separado. Exercita callback único, resposta perdida, vários slots,
troca de cookie, renovação explícita e fila central durante respostas atrasadas. Não altera jogos publicados.
Testes: SDK (referência interna; consulte o operador), HTTP/banco (referência interna; consulte o operador), processo/CLI (referência interna; consulte o operador) e navegador (referência interna; consulte o operador). Evidências executadas pertencem ao relatório (referência interna; consulte o operador); existência dos testes não é prova de sucesso.
Distribuição pública da oficina — 2026-10-18
O módulo e o exemplo são distribuídos sob MIT. A oficina em
/criar/sdk/ do hub prepara guia navegável e ZIP versionado sem exigir acesso ao
repositório privado. Este README permanece dono do contrato: o gerador
hub/tools/build_oficina.py copia módulo/exemplo byte a byte e adapta apenas links
na edição pública do guia, identificando referências internas não distribuídas.
Hashes de origem e dos arquivos do kit ficam no manifesto. Preparação local não
comprova publicação do hub ou dos endpoints de troféus. Provas da distribuição
pertencem ao guia do hub (referência interna; consulte o operador).
Evolução prevista
Primeira versão: transporte de conquista, sete slots fixos, cadastro por CLI, resgate e coleção central. Próxima integração: piloto escolhido e adaptado pelos criadores e abertura coordenada. Login Google está implementado separadamente e depende da configuração do ambiente; não faz parte da instalação do SDK. Nomes/descrições/ícones personalizados dos sete slots, painel de administração, paginação além de 1000 itens e atestado privado de um servidor de jogo são extensões, não requisitos já entregues. O legado de partidas/score continua disponível para compatibilidade; nenhuma partida antiga foi apagada ou convertida em troféu automaticamente.