SDK
@uloggd/arcade-sdk: o cliente, cada comando, evento, erro e código de fechamento, e o simulador.
npm install @uloggd/arcade-sdk- Licença MIT, sem dependências em tempo de execução. Mira ES2020 e pode ser importado durante a renderização no servidor.
- Vem como módulos ES e como CommonJS, ambos com declarações de tipos, e como um build para uma tag script comum (abaixo).
@uloggd/arcade-sdké o cliente,@uloggd/arcade-sdk/mocko simulador, e@uloggd/arcade-sdk/servera verificação de tokens para Node 20.19 ou mais novo, Deno, Bun e Workers (veja Identidade; para servidores em outras linguagens, veja Verificação em outras linguagens).- O uloggd nunca serve o SDK aos jogos: você o empacota, ou hospeda junto com o jogo.
| Versão | O que traz |
|---|---|
0.1.0 | Só módulos ES. |
0.2.0 | Acrescenta CommonJS, carregado por require, e o build para tag script, dist/uloggd-arcade.min.js. A API e o protocolo não mudam. |
Sem um bundler
O pacote inclui dist/uloggd-arcade.min.js, um build para uma tag
<script> comum que define uma única variável global, UloggdArcade.
Copie-o de node_modules/@uloggd/arcade-sdk/dist/ para o seu jogo, junto com
uloggd-arcade.min.js.map se quiser pilhas de erro legíveis, e sirva-o da
própria origem do jogo.
<script src="./uloggd-arcade.min.js"></script>
<script>
const arcade = UloggdArcade.createArcadeSDK({
appId: "ID_DO_SEU_APLICATIVO",
mockWhenStandalone: true,
});
arcade.ready().then(({ context }) => startGame(context));
</script>UloggdArcadetrazcreateArcadeSDK,ArcadeSDKError,ArcadeSDKMocke as constantes (SDK_VERSION,ERROR_CODES,HostCloseCode,GuestCloseCode,PROTOCOL_VERSION,SUBSCRIBABLE_EVENTSeDEFAULT_HOST_ORIGIN). A verificação de tokens não está nele: ela é do seu servidor.- Sirva-o você mesmo. O uloggd não o serve, e uma cópia numa CDN pública é um terceiro que o seu lançamento precisa declarar, e que pode mudar o que o jogo roda sem uma nova revisão.
- Sem o npm, o mesmo arquivo está em
https://registry.npmjs.org/@uloggd/arcade-sdk/-/arcade-sdk-0.2.0.tgz, empackage/dist/. - Carregue-o como script clássico, antes dos scripts que o usam. Como script
de módulo (
type="module"), ele não define nenhuma variável global. - Os módulos ES de
disttambém funcionam num script de módulo, que importadist/client.jscom a pastachunksao lado.
Criar o cliente
import { createArcadeSDK, ArcadeSDKError } from "@uloggd/arcade-sdk";
const arcade = createArcadeSDK({
appId: "ID_DO_SEU_APLICATIVO",
mockWhenStandalone: true,
});
try {
const { context } = await arcade.ready();
// context: { appId, instanceId, mode, locale, platform, layout, game }
startGame(context);
} catch (error) {
if (error instanceof ArcadeSDKError && error.code === "not_embedded") {
showMessage("Jogue este jogo no uloggd.com");
}
}| Opção | |
|---|---|
appId | O id do seu aplicativo, como o portal mostra: um uuid em minúsculas. |
hostOrigin | Onde o uloggd roda. https://uloggd.com por padrão; uma origem http:// só é aceita quando o próprio jogo roda numa origem de loopback. |
mockWhenStandalone | Aberto direto numa origem de loopback (localhost), fora de qualquer frame, o cliente é o simulador. Em qualquer outro lugar fora do uloggd, ready() rejeita com not_embedded, então um jogo publicado no lugar errado falha de forma visível. |
createArcadeSDK lança invalid_args na hora para um appId ou um
hostOrigin malformado.
| Membro | |
|---|---|
ready() | Resolve uma vez, depois do handshake, com { protocol, commands, context }. Rejeita com not_embedded, com timeout quando o uloggd não responde ao handshake em 10 segundos, ou com closed. |
context | O mesmo contexto, ou null antes de ready() resolver. |
commands | Os comandos abaixo. |
subscribe(event, listener, onError?) | Escuta um evento e devolve a função que para de escutar. |
supports(command) | Se o uloggd anunciou o comando em READY. Confira antes de usar um comando mais novo que o SDK que você publicou. |
close({ code, message? }) | Encerra a sessão, com um dos códigos de fechamento abaixo. |
destroy() | Encerra a sessão normalmente, com o código 1000, como close faria, e rejeita tudo o que está pendente. Para a limpeza de um efeito ou um hot reload. |
Comandos
Todo comando devolve uma promise e tem prazo: 10 segundos, ou 5 minutos para
authorize, que espera pelo jogador.
| Comando | Argumentos | Resultado | Exige |
|---|---|---|---|
authorize | { scopes: ["identify"] } | { scopes } | |
getPlayer | nenhum | { id, nickname } | identify |
getIdentityToken | { nonce } | { token, expiresAt } | identify |
getGame | nenhum | { appId, slug, name } | |
requestFullscreen | nenhum | { fullscreen } | um gesto |
exitFullscreen | nenhum | { fullscreen } |
authorizeabre o diálogo de consentimento do próprio uloggd, fora do frame, onde o jogador escolhe o apelido que o seu jogo vai ver (um gerado, por padrão). Sem a capacidadepointer-lock, o diálogo abre direto, ou assim que a aba voltar a aparecer, se estiver oculta. Com ela, o diálogo não abre sozinho, porque o uloggd não enxerga uma trava que o seu jogo segura: a barra do jogador mostra o pedido com um botão Responder, e o diálogo abre a partir desse clique. Peça só escopos que o lançamento declarou, senão ele falha cominvalid_args. Se o jogador cancela, ele falha comuser_cancelled, e pedir de novo continua falhando até o jogador abrir o jogo outra vez. Peça depois de um clique seu, como "Salvar meu progresso", e não ao carregar. Um jogador que já aceitou o seu jogo não é perguntado de novo.getPlayerdevolve o id por jogo e o apelido do jogador, sem verificação. Mostre-os; nunca confie neles no seu servidor.getIdentityTokendevolve um token assinado pelo uloggd, para o seu servidor verificar. O nonce vem do seu servidor: de 16 a 128 caracteres entre letras, dígitos,_e-. Um token a cada 3 segundos, e 30 por jogador por hora.getGamedevolve o nome aprovado e o slug, o endereço reservado quando o Arcade foi ativado. Hoje o slug nunca énull; o tipo admitenullpara versões futuras.requestFullscreeneexitFullscreenagem no contêiner do uloggd, com a barra dele visível. ChamerequestFullscreena partir de um clique ou de uma tecla, senão ele falha comrequires_gesture.
document.querySelector("#salvar")!.addEventListener("click", async () => {
await arcade.commands.authorize({ scopes: ["identify"] });
const { nonce } = await fetch("/session/nonce", { method: "POST" }).then(
(r) => r.json(),
);
const { token } = await arcade.commands.getIdentityToken({ nonce });
await fetch("/session", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ token }),
});
});Eventos
const off = arcade.subscribe("LAYOUT_UPDATE", (layout) => {
resize(layout.width, layout.height);
});
// depois
off();| Evento | Dados |
|---|---|
READY | { protocol: 1, commands, context }. Enviado uma vez, sem inscrição; ready() resolve com ele. |
VISIBILITY_UPDATE | { visible } |
LAYOUT_UPDATE | { width, height, fullscreen, safeArea: { top, right, bottom, left } } |
LOCALE_UPDATE | { locale } |
CLOSING | { reason, deadlineMs }, em que reason é user, hidden ou suspended. |
GRANT_UPDATE | { scopes } |
- O primeiro ouvinte de um evento faz a inscrição, e o último
off()a desfaz. Uma inscrição que o uloggd recusa remove os ouvintes dela e chamaonError. - O SDK também escreve a área segura em
:root, como--uloggd-safe-area-inset-top,-right,-bottome-left. CLOSINGdá atédeadlineMspara salvar.GRANT_UPDATEchega quando o jogador revoga o acesso do seu jogo nas Configurações. Pare de chamargetPlayeregetIdentityToken, e trate o jogador como desconectado do seu servidor.
Erros
Toda rejeição é um ArcadeSDKError, com um code:
| Código | Significa |
|---|---|
not_embedded | Fora do uloggd, e fora do simulador. |
unknown_command | O uloggd não conhece o comando; confira supports() antes. |
invalid_args | Os argumentos foram recusados: um escopo que o lançamento não declarou, um nonce malformado, uma chave a mais. |
missing_scope | O jogador não concedeu o escopo de que o comando precisa. |
user_cancelled | O jogador fechou o diálogo do uloggd. |
requires_gesture | O comando precisa antes de um clique ou de uma tecla no jogo. |
rate_limited | Comandos demais, ou tokens demais. |
timeout | Sem resposta a tempo. |
closed | A sessão fechou antes da resposta. Quando um código de fechamento a encerrou, o erro o traz em closeCode. |
unavailable | O uloggd não consegue fazer isso agora, como emitir tokens de identidade sem uma chave de assinatura disponível. |
internal | Algo falhou do lado do uloggd. |
Códigos de fechamento
O uloggd encerra a sessão com um destes, e tira o frame:
| Código | Significa |
|---|---|
4000 | Frame ou aplicativo inválido. |
4001 | Origem inválida. |
4002 | Mensagens demais. |
4003 | O jogador revogou o acesso do jogo. |
4004 | Versão de protocolo não suportada. |
4010 | O jogo foi suspenso ou desligado. |
O jogo pode fechar com 1000 (normal) ou com um código próprio de 4500 a
4599. A message, de até 200 caracteres, chega ao console do navegador só
no modo de teste: os jogadores só veem as palavras do próprio uloggd.
Limites
O SDK mantém você dentro deles, mas eles importam se você manda muitos
comandos de uma vez: cada mensagem tem até 64 KiB, no máximo 32 comandos
esperam resposta ao mesmo tempo, e os comandos ficam limitados a 20 por
segundo, com picos de 50. Passando disso, o uloggd encerra a sessão com
4002.
O simulador
Em localhost, com mockWhenStandalone: true, o cliente é um
ArcadeSDKMock, que tem a mesma interface:
- todo comando responde com dados fixos.
authorizeconcede na hora, como um jogador que aceita, e até lágetPlayeregetIdentityTokenfalham commissing_scope, como dentro do uloggd; ?mock_player=2no endereço da página dá um segundo jogador, então duas abas podem ser dois jogadores;- os tokens de identidade são assinados com uma chave descartável e
iss: "mock", que os verificadores do servidor recusam a não ser que você passeallowMock: true.
No build para tag script, o simulador é UloggdArcade.ArcadeSDKMock.
Nos testes, crie o simulador você mesmo e conduza-o. Além de appId, ele
aceita mode, locale, platform, player e scopes, para um jogador que
já aceitou:
import { ArcadeSDKError } from "@uloggd/arcade-sdk";
import { ArcadeSDKMock } from "@uloggd/arcade-sdk/mock";
const arcade = new ArcadeSDKMock({
appId: "ID_DO_SEU_APLICATIVO",
scopes: ["identify"],
});
arcade.setPlayer({
id: "6d0c0d6e-3f7a-4d4b-9d61-2f1f5b0a8c11",
nickname: "Ada",
});
arcade.overrideCommand("authorize", async () => {
throw new ArcadeSDKError("user_cancelled");
});
arcade.emit("VISIBILITY_UPDATE", { visible: false });