// Trocar o token do plicflix pelo jogador, no SEU servidor Node (18+). // Sem dependências: copie este arquivo para o seu projeto. // Guia: https://developers.plicflix.com/docs/SDK // // import { trocarIngresso } from "./plicflix.mjs"; // const r = await trocarIngresso(token); // lê PLICFLIX_CHAVE e PLICFLIX_API do ambiente // // ou: trocarIngresso(token, { chave: "pk_…", api: "https://api.plicflix.com" }) // // r = { jogador: { id: "pf_…", nome: "Paulo" }, jogo, partida, assinante, contexto: { ref, idioma, plataforma } } // // → crie/ache a conta pelo r.jogador.id e faça o login. // // O token vale 5 minutos ou UMA troca. Token inválido/vencido/usado: lança erro com .status = 401. // // "ENTRAR COM PLICFLIX" fora do app (OAuth 2.0 + PKCE — https://developers.plicflix.com/docs/OAUTH): // const { verificador, desafio, estado } = novoPKCE(); // guarde verificador+estado num cookie curto // res.redirect(urlDeEntrada({ cliente: "meu-jogo", volta: "https://meu-jogo.com/api/plicflix/volta", desafio, estado })); // // na volta (?code=…&state=…): confira o estado e // const r = await trocarCodigo(code, { cliente: "meu-jogo", volta, verificador }); // // r = { jogador: { id: "pf_…", nome }, jogo, assinante, access_token, … } — o MESMO id da entrada pelo app import { createHash, randomBytes } from "node:crypto"; const API = "https://api.plicflix.com"; const APP = "https://plicflix.com"; const ambiente = (k) => (typeof process !== "undefined" ? process.env?.[k] : undefined); /** * @param {string} token o que o navegador mandou (Plicflix.entrar / Plicflix.token()) * @param {{ chave?: string, api?: string }} opcoes chave = a sua chave de parceiro (pk_…); padrão: PLICFLIX_CHAVE e PLICFLIX_API do ambiente */ export async function trocarIngresso(token, { chave = ambiente("PLICFLIX_CHAVE"), api = ambiente("PLICFLIX_API") || API } = {}) { if (!chave?.startsWith("pk_")) throw new Error("plicflix: informe { chave: 'pk_…' } (a chave do parceiro)"); if (!/^pft_[A-Za-z0-9_-]{43}$/.test(String(token ?? ""))) throw Object.assign(new Error("plicflix: token inválido"), { status: 401 }); const r = await fetch(`${api.replace(/\/$/, "")}/v1/ingresso/trocar`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${chave}` }, body: JSON.stringify({ token }), signal: AbortSignal.timeout(8000), }); const j = await r.json().catch(() => ({})); if (!r.ok) throw Object.assign(new Error(`plicflix: ${j.erro ?? r.status}`), { status: r.status }); return j; } // ------------------------------------------------------------------ "Entrar com Plicflix" (OAuth 2.0 + PKCE) /** Um par PKCE novo (S256) e um state aleatório. */ export function novoPKCE() { const verificador = randomBytes(48).toString("base64url"); return { verificador, desafio: createHash("sha256").update(verificador).digest("base64url"), estado: randomBytes(16).toString("base64url") }; } /** * O endereço para mandar o navegador (302). * @param {{ cliente: string, volta: string, desafio: string, estado: string, prompt?: "consent", plicflix?: string }} o */ export function urlDeEntrada({ cliente, volta, desafio, estado, prompt, plicflix = ambiente("PLICFLIX_APP") || APP }) { const q = new URLSearchParams({ response_type: "code", client_id: cliente, redirect_uri: volta, state: estado, code_challenge: desafio, code_challenge_method: "S256", scope: "perfil" }); if (prompt) q.set("prompt", prompt); return `${plicflix.replace(/\/$/, "")}/oauth/authorize?${q}`; } /** * Troca o código da volta pelo jogador (com a chave pk_ do estúdio: cliente confidencial). * Código inválido/vencido (5 min)/usado, ou verificador errado: lança erro com .status = 400 e .codigo = "invalid_grant". * @param {string} codigo o ?code da volta (pfc_…) * @param {{ cliente: string, volta: string, verificador: string, chave?: string, api?: string }} o */ export async function trocarCodigo(codigo, { cliente, volta, verificador, chave = ambiente("PLICFLIX_CHAVE"), api = ambiente("PLICFLIX_API") || API }) { if (!/^pfc_[A-Za-z0-9_-]{20,}$/.test(String(codigo ?? ""))) throw Object.assign(new Error("plicflix: código inválido"), { status: 400, codigo: "invalid_grant" }); const corpo = new URLSearchParams({ grant_type: "authorization_code", code: codigo, redirect_uri: volta, client_id: cliente, code_verifier: verificador }); const r = await fetch(`${api.replace(/\/$/, "")}/v1/oauth/token`, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", ...(chave ? { Authorization: `Bearer ${chave}` } : {}) }, body: corpo, signal: AbortSignal.timeout(8000), }); const j = await r.json().catch(() => ({})); if (!r.ok) throw Object.assign(new Error(`plicflix: ${j.error_description ?? j.error ?? r.status}`), { status: r.status, codigo: j.error }); return j; }