Construa um jogo auto-hospedado
Ao final deste tutorial você terá um jogo Caputchin de verdade: um bundle JavaScript que roda no iframe em sandbox do widget, fala com o anfitrião pelo SDK de jogo, e reporta um trace que o servidor pode reexecutar. Diferente do modo manual, um jogo auto-hospedado pode proteger uma chave de site, porque sua rodada é reproduzível. Este tutorial cobre construir e hospedar o jogo; replay e proteção cobre o artefato que de fato liga o portão.
Leia rode seu próprio jogo para onde isto se encaixa. Você precisa de um lugar para hospedar um arquivo JavaScript estático (qualquer CDN ou host estático) e do widget na sua página.
O formato de um jogo auto-hospedado
Um jogo auto-hospedado é um bundle JavaScript autossuficiente que:
- Importa a função
registerdo@caputchin/game-sdke registra uma única fábrica de jogo. - Renderiza no contêiner que o widget dá à fábrica, usando o idioma, skin e configuração resolvidos passados junto.
- Joga de forma determinística sob a semente por rodada, registrando o jogo como um trace.
- Numa vitória, chama
bridge.pass({ trace })com esse registro.
O widget carrega seu bundle no seu iframe e invoca sua fábrica; a fábrica sendo chamada é o sinal de início (não há um evento de início separado para esperar).
1. Registre uma fábrica de jogo
Instale o SDK e chame register uma vez com sua fábrica. Você não passa o manifesto; o servidor resolve os presets e os entrega à fábrica.
import { register } from "@caputchin/game-sdk";
register((container, bridge, ctx) => {
// container — a DOM element inside the sandboxed iframe; render into it.
// bridge — push-only channel to the host (pass / error / setSize).
// ctx — the per-round context (seed + resolved locale/skin/config).
const cleanup = startGame(container, bridge, ctx);
// Return an optional cleanup function; the widget calls it on unmount.
return cleanup;
});npm install @caputchin/game-sdk2. Leia o contexto por rodada
O terceiro argumento da fábrica, ctx, carrega tudo o que seu jogo precisa para este visitante:
ctx.seed: a semente de replay por rodada. Derive toda aleatoriedade dela. Null quando o jogo roda fora de uma sessão verificada.ctx.locale: o objeto de idioma resolvido:ctx.locale._langmais suas chaves de string traduzidas.ctx.skin: o objeto de skin resolvido:ctx.skin._theme(semprelightoudark) mais suas chaves de cor e ativo.ctx.config: a configuração de jogabilidade resolvida (ou null).
Estes são os presets resolvidos para este visitante, produzidos pela Caputchin a partir do seu esquema e presets definidos no painel. Você lê suas próprias chaves deles; você não resolve nada você mesmo.
Se o seu layout precisa de um tamanho explícito, diga ao anfitrião uma vez depois do seu primeiro paint:
bridge.setSize(360, 480);3. Torne o jogo determinístico
Esta é a regra que torna um jogo capaz de proteger: o jogo precisa ser determinístico dada a semente e as entradas do jogador. Não chame Math.random() nem leia o relógio para nada que afete o resultado; derive cada escolha aleatória de ctx.seed. O servidor reexecuta sua lógica sob a mesma semente e o mesmo trace registrado e precisa chegar ao mesmo veredito.
// A tiny seeded PRNG; feed it ctx.seed so the server reproduces the run.
function makeRng(seed) {
let s = hashToInt(seed);
return () => (s = (s * 1103515245 + 12345) & 0x7fffffff) / 0x7fffffff;
}Registre as entradas que conduzem o resultado (em quais alvos o jogador acertou, em ordem, com timing se importa) à medida que você joga. Serialize esse registro para uma string: essa string é o seu trace.
4. Aprove com o trace
Quando o visitante vence, entregue o trace ao anfitrião. pass aceita um objeto com uma única string trace:
function onWin(traceString) {
bridge.pass({ trace: traceString });
}O trace é opaco para a plataforma: é qualquer string que só o seu jogo define, de modo que sua lógica, combinada com a semente, reproduza o resultado. O jogo não reporta uma pontuação aqui; a pontuação, se houver, é seu próprio assunto dentro do iframe.
Mais dois fatos sobre a ponte:
- O primeiro
passresgata a rodada; uma rodada falha ou abandonada é sinalizada simplesmente não chamandopass(não há métodofailnesta ponte). bridge.error({ code, message })é para uma falha interna do jogo (um ativo falhou em carregar, uma exceção), não uma derrota do jogador. Ele expõe um eventoerrorao anfitrião.
Depois que o anfitrião resgata a aprovação, ele libera o token do widget, e seu backend verifica esse token como de costume.
5. Empacote para a sandbox
O widget carrega exatamente uma URL de script, então tudo precisa estar nesse único arquivo. Configure seu bundler (esbuild, rollup, vite, webpack; qualquer um deles) para uma saída autossuficiente:
- inline os ativos como data URLs (sprites, sons, fontes);
- desabilite o code splitting;
- se você usa WASM, embuta-o como base64 e instancie a partir dos bytes.
Padrões que não funcionam dentro da sandbox, porque o iframe é de origem opaca com uma CSP estrita:
fetch('./sprite.png')relativo ao caminho, não há caminho de onde buscar;import('./chunk.js')dinâmico, a segunda URL é bloqueada;new Worker('./worker.js'), gere workers de URLsBlobinline em vez disso;- buscas em CDN externo em tempo de execução,
connect-srcas bloqueia.
6. Hospede o bundle e aponte o widget para ele
Sirva o arquivo construído do seu próprio host estático por https (http de loopback é permitido para dev local), depois aponte o widget para ele:
<caputchin-game
sitekey="cpt_pub_..."
game-src="https://cdn.example.com/my-game/game.js"
></caputchin-game>O widget carrega seu bundle no seu iframe em sandbox, invoca sua fábrica com o contexto, e espera pelo seu pass. Neste ponto o jogo roda e verifica, mas ainda não tem permissão de proteger uma chave.
7. Torne-o capaz de proteger
Para usar seu jogo auto-hospedado como um portão de verificação, mais duas coisas são necessárias, ambas cobertas a seguir:
- Registre-o como um jogo personalizado no painel (um id que você escolhe) e defina seu esquema e presets de campos para que o contexto carregue locale/skin/config reais.
- Envie um artefato de replay: uma build headless da lógica do seu jogo que o servidor roda para rederivar o veredito da semente e do trace. Veja replay e proteção.
Até que o artefato de replay seja enviado e passe na sua autoverificação, o jogo personalizado aparece como Não reproduzível e não pode proteger.
Veja também
- Replay e proteção: o artefato que liga o portão.
- Referência de esquema do painel: definir os campos que o contexto resolve.
- Modo manual: a alternativa mais leve, que não protege.
- Verificar no seu backend: confirmar o token que uma aprovação produz.