# Guia de integração do plicflix

**Para estúdios de jogos.** Este guia explica tudo o que é preciso para pôr um
jogo no plicflix: o jogador entra sem login, a tela de carregamento do plicflix
acompanha o jogo, a bolha do plicflix fica por cima dele, o jogador sai sem
perder o progresso e os amigos se encontram dentro do jogo.

SDK atual: **plicflix.js 2.4.0**.

> **Login fora do app?** O botão "Entrar com Plicflix" no seu site ou app
> nativo (OAuth 2.0 + PKCE, o mesmo jogador `pf_…`) está em [OAUTH](OAUTH.md).

> **É uma IA fazendo a integração?** Comece por [COMECE](COMECE): é o passo a
> passo completo, com os endereços de cada ambiente e o exemplo pronto para
> baixar. A lista de tudo, em texto puro: `https://developers.plicflix.com/llms.txt`.

---

## Sumário

1. [Como funciona](#1-como-funciona)
2. [Antes de começar: o cadastro](#2-antes-de-começar-o-cadastro)
3. [Começo rápido](#3-começo-rápido)
4. [Entrada: o jogador já chega logado](#4-entrada-o-jogador-já-chega-logado)
5. [Deixar o plicflix abrir o seu jogo](#5-deixar-o-plicflix-abrir-o-seu-jogo)
6. [A tela de carregamento](#6-a-tela-de-carregamento)
7. [A bolha do plicflix](#7-a-bolha-do-plicflix)
8. [Sair salvando](#8-sair-salvando)
9. [Onde o jogador está](#9-onde-o-jogador-está)
10. [Amigos: "Ir até" e "Chamar para cá"](#10-amigos-ir-até-e-chamar-para-cá)
11. [Referência do SDK](#11-referência-do-sdk)
12. [O protocolo de mensagens (sem o SDK em JavaScript)](#12-o-protocolo-de-mensagens-sem-o-sdk-em-javascript)
13. [A API do plicflix para o seu servidor](#13-a-api-do-plicflix-para-o-seu-servidor)
14. [Unity, Godot, jogos sem servidor e outros motores](#14-unity-godot-jogos-sem-servidor-e-outros-motores)
15. [Segurança](#15-segurança)
16. [Testar antes de publicar](#16-testar-antes-de-publicar)
17. [Lista de conferência](#17-lista-de-conferência)
18. [Versões do SDK](#18-versões-do-sdk)

---

## 1. Como funciona

```
 app plicflix ──(1) toque em Jogar──▶ API plicflix: abre a partida e cria um TOKEN de uso único
      │
      │ (2) abre o SEU jogo, no SEU servidor, POR CIMA do app (iframe na web, WebView no celular),
      ▼     com a tela de carregamento do plicflix na frente:
            https://seu-jogo.com/?ref=plicflix#plicflix=pft_…&pf_idioma=pt-BR&pf_plataforma=web
 seu jogo (navegador) ──(3) Plicflix.entrar("/api/plicflix/entrar")──▶ SEU servidor
                                                                        │ (4) POST /v1/ingresso/trocar
                                                                        │     Authorization: Bearer pk_…
                                                                        ▼
                                                    API plicflix ──▶ { jogador: { id, nome }, contexto }
 seu jogo ◀──(5) sessão criada (cookie) ────────────────────────────────┘
      │
      ├──(6) Plicflix.progresso() / Plicflix.pronto() ──▶ a tela de carregamento acompanha e sai
      ├──(7) Plicflix.onde({ texto, area, ponto }) ────▶ o plicflix sabe onde o jogador está
      ├──(8) Plicflix.aoSair(salvar) ◀─── "Sair do jogo" na bolha (o app espera você salvar)
      └──(9) Plicflix.aoIrPara(levar) ◀── um amigo quer vir até aqui (você leva o jogador até ele)
```

O que é de cada um:

| o plicflix faz | o seu jogo faz |
|---|---|
| conta, assinatura e catálogo | o jogo, no **seu** servidor (o plicflix nunca hospeda o jogo de ninguém) |
| abrir o jogo e a tela de carregamento | trocar o token e criar a sessão |
| a bolha por cima do jogo: avisos, amigos, sair, reclamar | salvar o progresso quando o jogador sai |
| medir o tempo de jogo e dividir os 60% ([REGRAS](REGRAS#repasse)) | contar onde o jogador está (opcional, mas recomendado) |
| vigiar se o seu servidor está no ar | levar o jogador até o amigo, se o seu jogo permitir |

**Só dois dados do jogador chegam ao seu jogo:**

| campo | exemplo | o que é |
|---|---|---|
| `id` | `pf_4olrydypdwdxj3adjnzjvgss` | O código do jogador **no seu jogo**. É fixo para essa pessoa e diferente em cada jogo, então dois estúdios não conseguem cruzar quem é quem. Use como chave da conta. |
| `nome` | `Paulo` | O apelido, com até 24 letras. Serve para mostrar na tela. |

E-mail, dados de pagamento e país nunca chegam ao jogo.

---

## 2. Antes de começar: o cadastro

Tudo começa no **plicflix developers** (`https://developers.plicflix.com`).
O caminho inteiro, passo a passo, está em [COMECE](COMECE). Em resumo:
1. crie a conta (você e o estúdio) e pague a fatura da taxa de entrada, que
   aparece na sua área;
2. gere a **chave de integração** (`pk_…`). Ela aparece uma vez e é secreta:
   fica **só no seu servidor**, nunca no navegador;
3. cadastre o jogo. Ele nasce **não publicado**: só os testadores que você
   indicar veem, na fileira "Em teste" do app;
4. monte a vitrine e envie as imagens e o trailer (com a qualidade mínima, veja
   [VITRINE](VITRINE));
5. com a lista de conferência toda verde, **envie para revisão**. A equipe
   plicflix testa e publica.

O que você informa ao cadastrar o jogo:

| campo | exemplo | para quê |
|---|---|---|
| identificador (`slug`) | `meu-jogo` | O nome do jogo na API. Volta como `jogo` na troca do token. Letras minúsculas, números e `-`. |
| endereço (`url`) | `https://meu-jogo.com/` | A página que o app abre. HTTPS (para testar antes de publicar, vale `http://localhost:PORTA/`). |
| rota de status (`status_url`) | `https://meu-jogo.com/saude` | Uma rota barata que responda 200. A vigia do plicflix consulta de minuto em minuto. |

Depois de cadastrado, a vitrine é editada no painel (ou por
`PUT /v1/parceiro/jogos/{slug}`, veja [API](API)).

**A vigia** marca o jogo assim:
- **online:** respondeu 2xx/3xx, ou 401/403, em menos de 2,5 s;
- **instável:** respondeu em mais de 2,5 s, ou falhou uma vez;
- **fora do ar:** falhou 2 vezes seguidas.

Com o jogo fora do ar, o app mostra "Fora do ar" e não deixa abrir (só os
testadores de um jogo não publicado conseguem abrir mesmo assim). Endereços
`localhost` não são vigiados.

---

## 3. Começo rápido

O mínimo para o jogador entrar logado, com a tela de carregamento e o sair
salvando:

**No navegador** — dois arquivos, sem script inline (assim a sua CSP pode ser
`script-src 'self'`):

```html
<!-- index.html -->
<script src="/plicflix.js"></script>          <!-- cópia de https://developers.plicflix.com/sdk/plicflix.js, sem mudar nada -->
<script type="module" src="/jogo.js"></script>
```

```js
// jogo.js (um módulo: pode usar await)
if (Plicflix.dentro()) {
  Plicflix.progresso(10, "Entrando…");
  try {
    await Plicflix.entrar("/api/plicflix/entrar");   // o SEU servidor troca o token e cria a sessão
  } catch (e) {
    // não deu (token vencido, ou recarregou depois de entrar): use a sessão que já existe
    // ou mostre o seu login de sempre
  }
  Plicflix.aoSair(() => salvarProgresso());          // o "Sair" da bolha espera você salvar
}
// … carregue o jogo …
Plicflix.pronto();                                   // tira a tela de carregamento do plicflix
```

**No servidor (Node, com Express):**

```js
import express from "express";
import { trocarIngresso } from "./plicflix.mjs";   // https://developers.plicflix.com/sdk/plicflix.mjs?baixar=1

const app = express();
app.use(express.json());

app.post("/api/plicflix/entrar", async (req, res) => {
  let r;
  try {
    r = await trocarIngresso(req.body.token);        // lê PLICFLIX_CHAVE e PLICFLIX_API do ambiente
  } catch (e) {
    return res.status(e.status === 401 ? 401 : 502).json({ erro: "abra o jogo de novo pelo plicflix" });
  }
  if (r.jogo !== "meu-jogo") return res.status(401).end();     // o seu slug
  // ache ou crie a conta pelo r.jogador.id ("pf_…", fixo) e guarde o r.jogador.nome
  const conta = await suaFuncaoAcharOuCriarConta(r.jogador.id, r.jogador.nome);
  const sessao = await suaFuncaoCriarSessao(conta);
  // o cookie do iframe (escrito à mão: a opção "partitioned" do res.cookie só existe no Express 5)
  res.setHeader("Set-Cookie", `sessao=${sessao}; Path=/; HttpOnly; Secure; SameSite=None; Partitioned; Max-Age=2592000`);
  res.json({ ok: true });
});
```

**Exemplo completo e executável** (Node sem dependências, ou Go): baixe
[`exemplo-node.zip`](/sdk/exemplo-node.zip) ou [`exemplo-go.zip`](/sdk/exemplo-go.zip).
Rode com `PLICFLIX_CHAVE=pk_… node server.mjs` e siga o README de dentro.

**E a permissão para o plicflix abrir o jogo**, que é o [passo 5](#5-deixar-o-plicflix-abrir-o-seu-jogo).

Com isso o jogo já funciona no plicflix. Os passos 9 e 10 (onde o jogador
está, amigos) deixam a experiência completa.

---

## 4. Entrada: o jogador já chega logado

### O que chega no endereço

O app abre `https://seu-jogo/?ref=plicflix#plicflix=pft_…&pf_idioma=pt-BR&pf_plataforma=android`.

- **`?ref=plicflix`:** vai para o seu servidor. Use nas suas métricas.
- **O fragmento (`#…`):** o navegador **nunca** envia ao seu servidor, então o
  token não aparece nos seus logs. O SDK lê o token e **apaga** o fragmento da
  barra de endereço.

### O token

- **Formato:** `pft_` seguido de 43 caracteres. É opaco: não carrega dado
  nenhum.
- **Validade:** **5 minutos ou uma troca**, o que vier primeiro.
- **Recarregar a página:** antes da troca, o SDK guarda o token no
  `sessionStorage` da aba. Depois da troca, `Plicflix.token()` vem `null` e o
  jogador segue com a **sua** sessão. `Plicflix.dentro()` continua `true`, por
  causa do `?ref=plicflix`.

### A troca, no seu servidor

```
POST https://api.plicflix.com/v1/ingresso/trocar
Authorization: Bearer pk_…
Content-Type: application/json

{ "token": "pft_…" }
```

**200**:

```json
{ "jogador": { "id": "pf_…", "nome": "Paulo" }, "jogo": "seu-slug", "partida": "pt_…",
  "assinante": true, "contexto": { "ref": "plicflix", "idioma": "pt-BR", "plataforma": "web" } }
```

**401**: o token é inválido, venceu, já foi trocado ou é de um jogo de outro
parceiro. É sempre a mesma resposta, de propósito, para não ajudar quem tenta
adivinhar tokens.

Os ajudantes já prontos (só a biblioteca padrão; leem `PLICFLIX_CHAVE` e
`PLICFLIX_API` do ambiente):
- **Node 18+:** [`plicflix.mjs`](/sdk/plicflix.mjs?baixar=1) →
  `await trocarIngresso(token)`;
- **Go:** [`plicflix.go`](/sdk/plicflix.go?baixar=1) →
  `plicflix.DoAmbiente().Trocar(ctx, token)`.

Outra linguagem? É um POST comum, como acima. Erros: `401` (token ruim, de
propósito sem detalhe), `400` (corpo inválido), `429` (limite), `5xx` (tente de
novo).

### A conta

- **Chave:** ligue a conta do seu jogo a `jogador.id`, numa coluna única (por
  exemplo `contas.plicflix_id`).
- **Sem senha utilizável:** quem entra pelo plicflix não precisa de senha. Gere
  uma senha aleatória que ninguém conhece.
- **Reserve o login interno:** se o login for um e-mail, use um domínio que
  ninguém consiga cadastrar (por exemplo `pf_…@plicflix.invalid`) e recuse esse
  domínio no seu cadastro normal. Sem isso, alguém poderia criar a conta antes
  e tomar o lugar do jogador.
- **O apelido muda:** atualize o nome a cada entrada.
- **Confira o jogo:** confira `jogo == seu slug` antes de abrir a sessão.

---

## 5. Deixar o plicflix abrir o seu jogo

Na web, o app abre o jogo num `<iframe>`, num **site diferente** do seu. Duas
coisas precisam estar certas.

**a) Permitir o iframe.** Tire o `X-Frame-Options: DENY` e liste as origens do
plicflix na CSP:

```
Content-Security-Policy: …; frame-ancestors 'self' https://plicflix.com
```

**As origens do plicflix** (o que vai no `frame-ancestors`):

| ambiente | origem |
|---|---|
| produção (o app que os jogadores e os testadores usam) | `https://plicflix.com` |

É só essa: `www.plicflix.com` e `app.plicflix.com` redirecionam para ela. O
verificador ("Conferir agora" no painel) confere exatamente isso.

**b) O cookie da sessão.** Dentro de um iframe de outro site, o navegador
**não envia** cookie `SameSite=Strict` nem `Lax`. Para a sessão criada pelo
plicflix, use:

```
Set-Cookie: sessao=…; Path=/; HttpOnly; Secure; SameSite=None; Partitioned
```

- **Por que `Partitioned`:** o cookie fica guardado só para "o seu jogo dentro
  do plicflix". Nenhum outro site o recebe.
- **Ao sair da conta,** apague os dois cookies: o normal e o `Partitioned`.
- **No celular,** a WebView do app abre o jogo como página principal, então o
  cookie normal também funciona. Mesmo assim, use o mesmo código.
- **Safari no computador:** pode recusar cookies de outro site. Teste nele. Se
  o seu jogo não depender de cookie (por exemplo, guarda a sessão em memória e
  manda num cabeçalho), melhor ainda.
- **`localhost` em teste:** os navegadores aceitam `Secure` em
  `http://localhost`, então o mesmo cookie funciona no teste local.

---

## 6. A tela de carregamento

Do toque em "Jogar" até o jogo dizer que está pronto, a tela do plicflix fica
**por cima**. Ela mostra o banner e o logo do seu jogo, a barra e os textos.

| o seu jogo faz | o app faz |
|---|---|
| carrega o SDK | entende que o jogo vai avisar (texto "Entrando…") |
| `Plicflix.progresso(40, "Carregando o mapa…")` | a barra em 40% e o seu texto |
| `Plicflix.pronto()` | a tela some, e o tempo de jogo começa a contar |
| `Plicflix.erro("Servidor em manutenção")` | a sua mensagem, com os botões "Tentar de novo" e "Voltar" |
| não usa o SDK | a tela some 2,5 s depois de a página carregar |
| usa o SDK, mas não chama `pronto()` | a tela some 12 s depois de a página carregar |
| passou de 25 s | "Está demorando mais que o normal": continuar esperando, tentar de novo (com outro token) ou voltar |

**Chame `pronto()` assim que o jogador puder jogar.** O tempo até o "pronto",
ou a falha, chega ao estúdio.



---

## 7. A bolha do plicflix

Por cima de todo jogo, o app mostra a **bolha do plicflix**:
- é um botão redondo que o jogador arrasta para onde quiser (a posição fica
  guardada);
- o tamanho depende do aparelho: 56 px no celular, 64 no tablet, 46 no
  computador;
- parada, ela fica semitransparente.

**O que ela mostra:**
- avisos do plicflix (com número de não lidos e um balão);
- o tempo jogado e onde o jogador está;
- os **amigos jogando agora**, com "Ir até" e "Chamar".

**O menu dela:** Sair do jogo, Recarregar, Tela cheia (na web) e Reclamar.

O que isso significa para o seu jogo:
- **Ela sempre fica por cima.** O jogador pode movê-la, mas não deixe nada que
  só funcione num canto fixo da tela.
- **Você não precisa fazer nada para ela aparecer.** Ela é do app, não do seu
  jogo.
- **Para abrir a lista de amigos da bolha**, por exemplo num botão "Chamar
  amigos" do seu jogo, use `Plicflix.abrirAmigos()`.

---

## 8. Sair salvando

Quando o jogador toca em **Sair do jogo** na bolha, ou no botão voltar do
celular ou do navegador:

```
app  → jogo   { tipo: "saindo" }   o SDK chama a sua função de Plicflix.aoSair
jogo → app    { tipo: "saiu" }     o SDK responde sozinho quando a função termina
app           mostra "Salvando seu progresso…" e fecha. Sem resposta em 5 s, fecha assim mesmo.
```

```js
Plicflix.aoSair(async () => {
  await salvarProgresso();   // ou: fechar a conexão do jogo direito, para o servidor gravar
});
```

- **Se o seu servidor já salva o tempo todo,** feche a conexão direito e
  espere o "fechou".
- **`Plicflix.sair()`** serve para o **jogo** fechar a si mesmo, por exemplo
  num botão "Voltar ao plicflix" dentro do jogo, depois de ter salvo.

---

## 9. Onde o jogador está

Conte ao plicflix onde o jogador está **sempre que ele mudar de lugar**:

```js
Plicflix.onde({
  texto: "Taverna do Porto",                    // o que as pessoas leem: até 60 letras
  area:  "cidade/taverna",                       // para agrupar: minúsculas, números e / _ . -  (até 60)
  ponto: { cena: "taverna:1", x: 12, y: 8 },     // só o SEU jogo entende: objeto JSON de até 1 KB
});
Plicflix.onde("Fase 3");                         // também vale só o texto
```

**O que o plicflix faz com isso:**

| campo | quem vê | para quê |
|---|---|---|
| `texto` | o jogador (na bolha) e os amigos (se a privacidade dele deixar) | "Ana — Jogando Ilha das Luzes · Taverna do Porto" |
| `area` | ninguém, por enquanto | agrupar (ex.: quantos estão na taverna) |
| `ponto` | **ninguém além do seu jogo** | volta para o seu jogo no "ir até" |

E ainda:
- **Continuar de onde parou:** o último "onde" de cada jogo aparece na fileira
  "Continuar jogando" da tela inicial ("📍 Taverna do Porto · há 2 h"), em
  qualquer aparelho. O progresso continua sendo do seu jogo; o plicflix guarda
  só o marcador.
- **Sem repetição:** o mesmo "onde" seguido não é reenviado, e o app manda no
  máximo um a cada 1,5 s. Pode chamar à vontade.
- **Privacidade:** cada jogador escolhe quem vê onde ele está (amigos, todos
  ou ninguém). Veja [SOCIAL](SOCIAL).

Exemplo: a cada troca de cena, mande o nome do lugar, uma área
(`cidade`, `interior/casa`, `batalha`) e `{ cena }`.

---

## 10. Amigos: "Ir até" e "Chamar para cá"

Os jogadores do plicflix têm amigos, adicionados pelo **código de amigo**
(8 letras, por exemplo `84A621CA`). Na bolha e na tela Amigos aparece onde cada
um está. Daí vêm dois botões:

- **Ir até:** abre o jogo do amigo, se ainda não for o mesmo, e pede ao jogo
  para levar o jogador até ele.
- **Chamar para cá:** o amigo recebe um aviso na bolha, com o botão "Ir". Isso
  libera o "ir até" por 24 h, mesmo que a privacidade de quem chamou esteja
  fechada.

### O que o seu jogo recebe

```js
Plicflix.aoIrPara(async (destino) => {
  // destino = {
  //   jogador_id: "pf_…",          o código de QUEM ESTÁ LÁ, no SEU jogo (o mesmo id da troca do token)
  //   jogador: "Ana",              o apelido
  //   texto: "Taverna do Porto", area: "cidade/taverna",
  //   ponto: { cena: "taverna:1", x: 12, y: 8 },   o que você mesmo mandou em Plicflix.onde (pode faltar)
  //   jogo: "seu-slug"
  // }
  const r = await fetch("/api/plicflix/ir", {
    method: "POST", credentials: "same-origin",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ jogador_id: destino.jogador_id }),
  }).then((x) => x.json());
  return { ok: r.ok, motivo: r.motivo };   // o motivo aparece para o jogador
});
```

**O que devolver:**
- `true` ou `{ ok: true }`: o app mostra "Você foi até Ana.";
- `false` ou `{ ok: false, motivo: "Ela está numa terra particular." }`: o app
  mostra o motivo.

A função pode devolver uma Promise. Se o jogo não registrar `aoIrPara`, o SDK
responde sozinho "Este jogo ainda não sabe levar você até outra pessoa".

### A regra de segurança mais importante

> **Nunca teletransporte o jogador pelo `ponto` vindo do navegador.** Um
> trapaceiro manda qualquer coordenada. Mande o **`jogador_id`** para o **seu
> servidor**, ache lá o jogador online com esse código e leve até a posição
> **real** dele. Assim só dá para ir até alguém que está de fato no jogo.

O seu servidor também decide **se pode**:
- lugares privados, como a casa ou a terra de outro jogador;
- batalhas, partidas ranqueadas e instâncias cheias;
- um intervalo entre um "ir até" e outro.

O motivo de cada recusa volta para o jogador.

### Quando chega o pedido

- **Com o jogo aberto:** o jogador está jogando e toca em "Ir até" na bolha.
  A mensagem chega na hora.
- **Abrindo o jogo:** o jogador estava fora e tocou em "Ir até" na tela
  Amigos. A mensagem chega logo depois do seu `Plicflix.pronto()`.
  - Se o jogador ainda não estiver "no mundo", por exemplo criando o
    personagem, **espere ele entrar** e só então leve (o `aoIrPara` pode
    demorar: a Promise pode esperar alguns minutos).
- **Amigo noutro jogo:** se o amigo está em **outro** jogo, o app pergunta se o
  jogador quer sair deste. O seu `aoSair` roda normalmente, e o app abre o
  outro jogo.

---

## 11. Referência do SDK

| função | quando usar |
|---|---|
| `Plicflix.dentro()` | `true` quando o jogo foi aberto pelo plicflix. |
| `Plicflix.token()` | O token de uso único, ou `null`. |
| `Plicflix.contexto()` | `{ ref: "plicflix", idioma, plataforma }` (`web`, `android` ou `ios`). |
| `Plicflix.entrar(url, { credentials?, headers? })` | Faz `POST url` com `{ token, contexto }` (`credentials: "include"` por padrão). Devolve o JSON do seu servidor ou lança um erro com `.status`. Depois disso o token é esquecido. |
| `Plicflix.jogador()` | **Jogo sem servidor:** troca o token direto da página e devolve `{ jogador: { id, nome }, jogo, contexto, pelo_navegador: true }`. Veja a seção 14. |
| `Plicflix.progresso(pct, texto)` | Avança a barra (0 a 100) e troca o texto da tela de carregamento. |
| `Plicflix.pronto()` | Tira a tela de carregamento. |
| `Plicflix.erro(texto)` | Mostra a sua mensagem e oferece "tentar de novo". |
| `Plicflix.aoSair(fn)` | Roda antes de fechar: salve ali (espera de até 5 s). |
| `Plicflix.sair()` | O jogo fecha a si mesmo e volta ao catálogo. |
| `Plicflix.onde(texto \| { texto, area?, ponto? })` | Onde o jogador está. |
| `Plicflix.aoIrPara(fn)` | Leve o jogador até `destino` e devolva `true`, `false` ou `{ ok, motivo }`. |
| `Plicflix.abrirAmigos()` | Abre a lista de amigos da bolha. |
| `Plicflix.entrarComPlicflix({ cliente, volta, prompt? })` | **Fora do app:** vai para o "Entrar com Plicflix" (OAuth + PKCE). Veja [OAUTH](OAUTH.md). |
| `Plicflix.voltouDoPlicflix({ servidor? })` | Na página de volta: confere o `state` e troca o código (no seu servidor ou direto). `null` sem `?code`. |
| `Plicflix.botaoEntrar(onde, { cliente, volta } \| { href })` | O botão oficial "Entrar com Plicflix". |
| `Plicflix.versao` | `"2.4.0"` |

Como distribuir o SDK:
- **Copie** [`plicflix.js`](/sdk/plicflix.js?baixar=1) para o seu site **sem
  mudar nada**. Assim ele passa pela sua CSP `script-src 'self'`.
- **Tipos TypeScript:** [`plicflix.d.ts`](/sdk/plicflix.d.ts?baixar=1).
- **Ou carregue da CDN** (`https://sdk.plicflix.com/plicflix.js`). Nesse caso,
  libere essa origem no `script-src`.

O SDK não tem dependências e funciona igual no iframe (web) e na WebView
(celular).

---

## 12. O protocolo de mensagens (sem o SDK em JavaScript)

O SDK é só uma camada fina sobre mensagens. Um jogo que não roda JavaScript
puro (um motor que expõe pontes, por exemplo) pode falar o protocolo direto.
Toda mensagem do jogo leva `v: 2`.

### Os canais

| sentido | web (iframe) | celular (WebView) |
|---|---|---|
| jogo → app | `window.parent.postMessage({ plicflix: msg }, "*")` | `window.PlicflixApp.postMessage(JSON.stringify(msg))` |
| app → jogo | `message` com `event.data.plicflixApp = msg`, vindo de `window.parent` | o app chama `window.__plicflixApp(json)` |

O app só aceita mensagens vindas **da origem do seu jogo** (a da `url`
cadastrada).

### As mensagens

| mensagem | sentido | campos |
|---|---|---|
| `ola` | jogo → app | `versao`, `recursos[]`. Avisa que o jogo usa o SDK. |
| `progresso` | jogo → app | `pct` (0 a 100), `texto` |
| `pronto` | jogo → app | — |
| `erro` | jogo → app | `texto` |
| `onde` | jogo → app | `texto`, `area?`, `ponto?` |
| `sair` | jogo → app | — |
| `abrirAmigos` | jogo → app | — |
| `saindo` | app → jogo | — (responda com `saiu`) |
| `saiu` | jogo → app | — |
| `irPara` | app → jogo | `destino` (veja a seção 10), `pedido` (número) |
| `foiPara` | jogo → app | `ok`, `motivo?`, `pedido` (o mesmo do `irPara`) |

---

## 13. A API do plicflix para o seu servidor

Todas as rotas usam `Authorization: Bearer pk_…`. A lista completa está em
[API](API) e, para máquinas, em [`/openapi.json`](/openapi.json).

| rota | o quê |
|---|---|
| `POST /v1/ingresso/trocar` `{token}` | A troca do token (seção 4). |
| `GET /v1/parceiro/jogos` | Os seus jogos. |
| `PUT /v1/parceiro/jogos/{slug}` | Edita a vitrine (veja [VITRINE](VITRINE)). Não muda `url` nem `tipo`. |
| `GET /v1/parceiro/casos` | Casos de reclamação, com motivos e prazo (veja [REGRAS](REGRAS#reclamações)). |
| `POST /v1/parceiro/casos/{id}/prova` `{prova}` | A prova da correção, dentro de 48 horas úteis. |
| `GET /v1/parceiro/repasses` | O extrato por mês (veja [REGRAS](REGRAS#repasse)). |

---

## 14. Unity, Godot, jogos sem servidor e outros motores

Está tudo em [MOTORES](MOTORES), com arquivos prontos para baixar. Resumo:

| o seu jogo | o que fazer |
|---|---|
| web com servidor (Node, Go, PHP, Python…) | este guia, como está |
| **Unity WebGL** | [`Plicflix.jslib`](/sdk/unity/Plicflix.jslib?baixar=1) + [`Plicflix.cs`](/sdk/unity/Plicflix.cs?baixar=1) |
| **Godot 4 (Web)** | [`plicflix.gd`](/sdk/godot/plicflix.gd?baixar=1) como Autoload |
| **sem servidor** (só arquivos estáticos) | `Plicflix.jogador()` no lugar do `entrar` — não protege nada de valor |
| Flutter web, Construct, Phaser, PixiJS, Three.js… | chame o `window.Plicflix` como em JavaScript (Flutter: `dart:js_interop`) |
| **só app nativo** (Android/iOS/PC sem versão web) | ainda não dá: o plicflix abre jogos pela web |

## 15. Segurança

1. **A chave `pk_` fica só no servidor.** Nunca no navegador nem no app.
2. **Confira o token no servidor.** O que vem do navegador pode ser
   falsificado; quem diz quem é o jogador é a troca do token.
3. **Confira o `jogo`** na resposta da troca.
4. **O `ir até` vai pelo `jogador_id`, no servidor.** Nunca pelo `ponto` vindo
   do navegador.
5. **Reserve o login interno das contas do plicflix** e recuse esse domínio no
   cadastro normal.
6. **O `frame-ancestors` lista só as origens do plicflix,** nunca `*`.
7. **O cookie `SameSite=None` vai só na sessão criada pelo plicflix,** e
   sempre `Partitioned`.
8. **O que o plicflix nunca manda:** e-mail, dados de pagamento ou a conta
   plicflix do jogador. Não peça.

---

## 16. Testar antes de publicar

O teste é no **plicflix de verdade** (`https://plicflix.com`), como testador
do seu próprio jogo. Não precisa de assinatura nem de publicar.

1. **No painel** (developers), cadastre o jogo. Enquanto testa, o endereço pode
   ser a sua máquina: `http://localhost:4100/` (e a rota de status,
   `http://localhost:4100/saude`).
2. **Na aba Testadores,** adicione o e-mail da sua conta do plicflix (crie uma
   em `https://plicflix.com` e confirme o e-mail com o código).
3. **Rode o jogo** na sua máquina com a chave: `PLICFLIX_CHAVE=pk_… node server.mjs`.
   O `frame-ancestors` precisa ter `https://plicflix.com`.
4. **Abra `https://plicflix.com`** com a conta testadora: o jogo aparece em
   **"Em teste (só você vê)"**. Toque em **Jogar**. O app abre o seu
   `localhost` dentro dele (o Chrome e o Edge aceitam `http://localhost` dentro
   de uma página https).
5. **Confira no painel, aba Integração:** a "Entrada pelo SDK" e a "Tela de
   carregamento" ficam verdes quando acontecem de verdade.
6. **Amigos:** com duas contas testadoras em duas janelas (uma anônima),
   adicione uma à outra pelo código de amigo, entre no jogo com uma e use
   "Ir até" na outra.
7. **Antes de enviar para revisão,** ponha o jogo num endereço público com
   https e troque no painel: aba **Integração → Endereço do jogo** (o item
   "Endereço público" da lista trava o envio enquanto for `localhost`). Depois
   de trocar, espere a vigia ver o servidor no ar (até 2 minutos).

**Sem o app** (num teste automático, por exemplo), dá para fazer a parte do
jogador pela API com a conta testadora — veja [API](API#testar-pela-api).

---

## 17. Lista de conferência

**Obrigatório**
1. `plicflix.js` carregado antes do jogo.
2. `Plicflix.entrar("/sua/rota")` antes do seu login.
3. A rota no servidor que troca o token com a `PLICFLIX_CHAVE` e confere o
   `jogo`.
4. A conta ligada a `jogador.id`, sem senha utilizável, e o login interno
   reservado.
5. `frame-ancestors` com as origens do plicflix, e nenhum
   `X-Frame-Options: DENY`.
6. O cookie da sessão do plicflix com `SameSite=None; Secure; Partitioned`,
   apagado também ao sair da conta.
7. `Plicflix.pronto()` quando o jogador puder jogar.
8. `Plicflix.aoSair` salvando o progresso.
9. Uma `status_url` que responde 200.

**Recomendado**
10. `Plicflix.progresso` durante o carregamento, e `Plicflix.erro` quando não
    der para abrir.
11. `Plicflix.onde({ texto, area, ponto })` a cada troca de lugar.
12. `Plicflix.aoIrPara` levando até o amigo pelo `jogador_id`, no servidor, só
    para lugares permitidos e com motivo claro quando recusar.
13. Um botão "Chamar amigos" no jogo com `Plicflix.abrirAmigos()`.

**Teste final**
14. Abra pelo app, na web e no celular:
    - entra sem pedir login;
    - a tela de carregamento sai sozinha;
    - a bolha arrasta;
    - "Sair do jogo" fecha em menos de 5 s;
    - com duas contas amigas, "Ir até" leva uma até a outra.

---

## 18. Versões do SDK

| versão | o que mudou |
|---|---|
| **2.4.0** | "Entrar com Plicflix" fora do app: `entrarComPlicflix`, `voltouDoPlicflix`, `botaoEntrar` ([OAUTH](OAUTH.md)). |
| 2.3.0 | `Plicflix.jogador()` (jogos sem servidor) e `pf_api` no endereço. |
| 2.2.0 | `onde` aceita objeto (`texto`, `area`, `ponto`) e não repete o mesmo lugar seguido. Novidades: `aoIrPara` (ir até o amigo, com resposta `foiPara`), `abrirAmigos`, e `recursos[]` no `ola`. |
| 2.1.0 | `aoSair` (sair salvando) e `onde` (só texto). |
| 2.0.0 | O token opaco de uso único trocado pelo servidor do jogo, mais `progresso`, `pronto` e `erro`. |
| 1.x | (descontinuado) ingresso JWT assinado, conferido pela chave pública. |

As versões novas não quebram as antigas: um jogo com a 2.0 continua funcionando
com o app atual, só sem os recursos novos.
