@pixelume/sdk 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,32 @@ conter mudanças incompatíveis, sempre listadas em **Changed/Removed**.
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.0] - 2026-10-13
11
+
12
+ ### Added
13
+
14
+ - `Pixelume` como alias conciso e `PixelumeClient.initialize()` idempotente.
15
+ - `client.auth` com Authorization Code + PKCE S256: login por redirecionamento, callback validado,
16
+ consulta de jogador, revogação/logout, estado observável e assinatura cancelável.
17
+ - Restauração após recarga por nova autorização SSO, sem persistir access token; somente um marcador
18
+ não sensível e o estado PKCE transitório ficam em `sessionStorage`.
19
+ - Configuração tipada de `clientId`, `redirectUri`, `authUrl`, `restoreSession` e adaptador de browser.
20
+ - Exemplo Vite com fluxo de autenticação completo e auditoria/documentação do contrato real.
21
+
22
+ ### Changed
23
+
24
+ - O `kind` de `PixelumeError` ganhou o valor aditivo `auth`; consumidores `0.x` com `switch`
25
+ exaustivo devem tratar esse novo caso.
26
+ - O cliente HTTP interno passou a serializar formulários OAuth URL-encoded.
27
+
28
+ ## [0.1.1] - 2026-10-13
29
+
30
+ ### Changed
31
+
32
+ - README do pacote: instalação a partir do npm (a 0.1.0 trazia a instrução provisória de instalar
33
+ via `npm pack`). Sem mudanças de código ou de API pública.
34
+ - Primeira versão publicada pelo workflow de release com npm Trusted Publishing (OIDC).
35
+
10
36
  ## [0.1.0] - 2026-10-13
11
37
 
12
38
  ### Added
@@ -21,5 +47,7 @@ conter mudanças incompatíveis, sempre listadas em **Changed/Removed**.
21
47
  `invalid_response`) e `isPixelumeError()`.
22
48
  - Build ESM + `.d.ts`, CI e workflow de publicação via npm Trusted Publishing.
23
49
 
24
- [Unreleased]: https://github.com/ericksonnascimento/pixelume-sdk/compare/v0.1.0...HEAD
50
+ [Unreleased]: https://github.com/ericksonnascimento/pixelume-sdk/compare/v0.2.0...HEAD
51
+ [0.2.0]: https://github.com/ericksonnascimento/pixelume-sdk/compare/v0.1.1...v0.2.0
52
+ [0.1.1]: https://github.com/ericksonnascimento/pixelume-sdk/compare/v0.1.0...v0.1.1
25
53
  [0.1.0]: https://github.com/ericksonnascimento/pixelume-sdk/releases/tag/v0.1.0
package/README.md CHANGED
@@ -1,27 +1,8 @@
1
1
  # @pixelume/sdk
2
2
 
3
- SDK TypeScript dos jogos web do estúdio **Pixelume**. Fornece um cliente tipado, seguro e sem
4
- dependências de runtime para a [Pixelume API](https://api.pixelume.com.br), independente de framework ou
5
- engine (funciona com Vite, Three.js, React, JavaScript puro, SSR e workers).
6
-
7
- > **Status: Fase 1 — fundação.** Esta versão oferece configuração, cliente HTTP, erros padronizados e a
8
- > checagem de disponibilidade da API. Login, sessão, cloud saves e analytics **ainda não existem** e
9
- > serão adicionados quando os contratos da API estiverem prontos (veja o [roadmap](docs/roadmap.md)).
10
-
11
- ## Capacidades
12
-
13
- | Capacidade | Fase 1 | Observação |
14
- | -------------------------------------------- | :----: | ------------------------------------------------------- |
15
- | `PixelumeClient` com configuração validada | ✅ | `apiUrl`, `gameId`, `timeoutMs`, `credentials`, `fetch` |
16
- | `client.health.check()` (`GET /health`) | ✅ | sem CORS na API: servidor/mesma origem (ver abaixo) |
17
- | Erros padronizados, timeout e cancelamento | ✅ | `PixelumeError` + `kind` |
18
- | Autenticação / Pixelume Auth | — | aguarda portal central e contratos de redirect |
19
- | Cloud saves, analytics, rankings, conquistas | — | aguardam contratos na API |
20
-
21
- ## Pré-requisitos
22
-
23
- - Navegadores modernos (Chrome/Edge 98+, Firefox 97+, Safari 15.4+) ou Node.js 22+, Deno, Bun, Workers.
24
- - Projeto ESM (o pacote é ESM-only, com tipos incluídos). Em CommonJS use `await import('@pixelume/sdk')`.
3
+ SDK TypeScript headless para jogos web da Pixelume. A versão 0.2 adiciona autenticação centralizada
4
+ com OAuth 2.0 Authorization Code + PKCE S256, além do cliente HTTP e health check da Fase 1. Não há
5
+ dependências de runtime, componentes visuais, senha no jogo nem `client_secret`.
25
6
 
26
7
  ## Instalação
27
8
 
@@ -29,97 +10,149 @@ engine (funciona com Vite, Three.js, React, JavaScript puro, SSR e workers).
29
10
  npm install @pixelume/sdk
30
11
  ```
31
12
 
32
- > O pacote **ainda não está publicado** no npm (veja [publicação](docs/publishing.md)). Até lá, gere o
33
- > tarball neste repositório e instale-o no jogo:
34
- >
35
- > ```bash
36
- > npm pack # gera pixelume-sdk-0.1.0.tgz
37
- > npm install /caminho/para/pixelume-sdk-0.1.0.tgz # no projeto do jogo
38
- > ```
13
+ O pacote é ESM-only, inclui tipos e funciona em Vite, JavaScript/TypeScript puro e engines web. Em
14
+ CommonJS, use `await import('@pixelume/sdk')`.
39
15
 
40
- ## Uso
16
+ ## Configuração obrigatória no backend
41
17
 
42
- ```ts
43
- import { PixelumeClient } from '@pixelume/sdk';
18
+ Antes de integrar um jogo, registre na Pixelume API:
44
19
 
45
- const pixelume = new PixelumeClient({
46
- apiUrl: 'https://api.pixelume.com.br', // padrão
47
- gameId: 'rail-puzzle', // opcional nesta fase
48
- });
20
+ - um `client_id` público, normalmente igual ao `gameId`;
21
+ - o escopo `profile`;
22
+ - cada callback completo por igualdade exata de scheme, host, porta, path e query.
49
23
 
50
- const health = await pixelume.health.check();
51
- // { status: 'ok', registration: 'email-verification', release: 'd99d195…' }
52
- ```
24
+ Não são aceitos curingas, prefixos ou callbacks enviados dinamicamente. O cliente local já cadastrado é
25
+ `pixelume-local-dev`, exclusivamente com `http://localhost:4173/oauth/callback` e
26
+ `http://127.0.0.1:4173/oauth/callback`. Jogos publicados exigem migration/configuração revisada na API.
53
27
 
54
- Saída real obtida pelo exemplo em 2026-10-13 (Node.js, pacote instalado via `npm pack`):
28
+ ## Uso
55
29
 
56
- ```json
57
- {
58
- "sdk": "0.1.0",
59
- "apiUrl": "https://api.pixelume.com.br",
60
- "health": {
61
- "status": "ok",
62
- "registration": "email-verification",
63
- "release": "d99d19543620f09309268f67d959e874ddc78ecf"
64
- }
65
- }
66
- ```
30
+ ```ts
31
+ import { Pixelume, isPixelumeError } from '@pixelume/sdk';
32
+
33
+ const pixelume = new Pixelume({
34
+ gameId: 'rail-puzzle',
35
+ // Use clientId somente quando for diferente do gameId.
36
+ // clientId: 'rail-puzzle.web',
37
+ // Padrão no browser: `${location.origin}/oauth/callback`.
38
+ redirectUri: 'https://rail-puzzle.pixelume.com.br/oauth/callback',
39
+ });
67
40
 
68
- ### `health.check()` no navegador
41
+ const initial = await pixelume.initialize();
42
+ // `initialize()` nunca impede o jogo de abrir: falhas de callback viram estado anonymous/error.
69
43
 
70
- A API ainda **não envia cabeçalhos CORS em `/health`** (só em `/v1/*`). Em uma página servida por outra
71
- origem, o navegador bloqueia a resposta e o SDK rejeita com `PixelumeNetworkError`. Use `health.check()`
72
- no servidor (Node/SSR/workers), em ferramentas, ou na mesma origem via proxy — o
73
- [exemplo Vite](examples/vite-basic) usa o proxy do `vite dev`. A correção depende da API
74
- ([auditoria](docs/api-contract-audit.md)).
44
+ const unsubscribe = pixelume.auth.subscribe((state, previous) => {
45
+ if (state.status !== previous.status) renderAccountState(state);
46
+ });
75
47
 
76
- ### Configuração
48
+ const player = await pixelume.auth.getPlayer(); // PixelumePlayer | null
77
49
 
78
- | Opção | Padrão | Descrição |
79
- | ------------------- | ----------------------------- | -------------------------------------------------------------------------------------------- |
80
- | `apiUrl` | `https://api.pixelume.com.br` | `https:` obrigatório; `http:` aceito só para `localhost`/`127.0.0.1`/`[::1]`/`*.localhost`. |
81
- | `gameId` | — | slug público (`a-z`, `0-9`, `-`, até 64). Identifica o jogo; **não** é credencial. |
82
- | `timeoutMs` | `10000` | tempo máximo de cada operação, incluindo leitura do corpo. |
83
- | `credentials` | `'omit'` | política de cookies do `fetch` para módulos futuros. O SDK não presume cookies cross-origin. |
84
- | `fetch` | `globalThis.fetch` | implementação injetável (testes, SSR, instrumentação). |
85
- | `allowInsecureHttp` | `false` | permite `http:` em hosts não-loopback (ex.: IP da LAN). Nunca em produção. |
50
+ loginButton.addEventListener('click', () => {
51
+ void pixelume.auth.login(); // navegação de topo para a API e o portal Auth
52
+ });
86
53
 
87
- Opções inválidas lançam `PixelumeConfigError` no construtor. Cada operação aceita
88
- `{ signal?: AbortSignal; timeoutMs?: number }`.
54
+ logoutButton.addEventListener('click', async () => {
55
+ try {
56
+ await pixelume.auth.logout(); // revoga somente o token deste jogo
57
+ } catch (error) {
58
+ if (!isPixelumeError(error)) throw error;
59
+ showRecoverableError(error.kind);
60
+ }
61
+ });
89
62
 
90
- ### Erros
63
+ // Ao desmontar a tela:
64
+ unsubscribe();
65
+ ```
91
66
 
92
- Todas as falhas são `PixelumeError` com um `kind` estável:
67
+ `pixelume.auth.state.status` é `uninitialized`, `redirecting`, `exchanging`, `anonymous` ou
68
+ `authenticated`. O estado anônimo informa `reason` (`visitor`, `cancelled`, `expired`, `logged_out` ou
69
+ `error`) e, em falha, um `PixelumeError`. Cancelar no portal resulta em visitante, não em exceção.
70
+
71
+ ### Visitante, recarga e expiração
72
+
73
+ - Sem login ou callback, `initialize()` termina como visitante e não faz requisição.
74
+ - O access token opaco dura 10 minutos e fica **somente em memória**.
75
+ - Após autenticar, o SDK guarda em `sessionStorage` apenas o marcador não secreto de que aquela aba
76
+ tinha uma sessão. Em uma recarga, inicia uma nova autorização; enquanto a sessão central da API
77
+ continuar válida, o portal retorna sem pedir senha. `restoreSession: false` desliga esse comportamento.
78
+ - PKCE `state`/`code_verifier` ficam na sessão por no máximo 10 minutos e são removidos antes da troca.
79
+ - Código expirado/repetido, `state` divergente, callback adulterado e 401 deixam o jogo utilizável como
80
+ visitante. O jogo pode oferecer o login novamente.
81
+ - `logout()` limpa o estado local imediatamente e chama `/v1/oauth/revoke`. A sessão central do portal
82
+ continua existente por desenho; entrar novamente pode ser imediato.
83
+
84
+ ## Configuração
85
+
86
+ | Opção | Padrão | Observação |
87
+ | ---------------- | ------------------------------------ | ----------------------------------------------------------------------------------------- |
88
+ | `apiUrl` | `https://api.pixelume.com.br` | HTTPS; HTTP só em loopback ou com `allowInsecureHttp` explícito |
89
+ | `authUrl` | `https://auth.pixelume.com.br` | Portal esperado; a API continua sendo a autoridade que escolhe o redirect |
90
+ | `gameId` | — | slug público do jogo; também é o `clientId` padrão |
91
+ | `clientId` | `gameId` | identificador OAuth público cadastrado; nunca é credencial |
92
+ | `redirectUri` | `<origin>/oauth/callback` no browser | deve coincidir exatamente com o cadastro e não pode conter fragmento nem parâmetros OAuth |
93
+ | `restoreSession` | `true` | reautoriza após reload da mesma aba sem persistir token |
94
+ | `timeoutMs` | `10000` | cobre resposta e leitura do corpo |
95
+ | `credentials` | `omit` | política dos módulos genéricos; Auth força `omit` para token/userinfo/revoke |
96
+ | `fetch` | `globalThis.fetch` | injetável para testes/instrumentação |
97
+ | `browser` | globals do browser | adaptador avançado para webviews e testes |
98
+
99
+ `authUrl` é informativo/configurável para ambientes, mas o SDK nunca concatena nem escolhe um retorno
100
+ via portal: `/v1/oauth/authorize` valida o cliente/callback e redireciona para o portal configurado na
101
+ API. Isso evita transformar o SDK em fonte de open redirect.
102
+
103
+ ## Desenvolvimento local e produção
104
+
105
+ | Ambiente | Cliente/callback | API/portal |
106
+ | ---------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------- |
107
+ | exemplo local | `pixelume-local-dev` + `http://localhost:4173/oauth/callback` | produção por padrão; callbacks loopback já cadastrados |
108
+ | API/Auth locais | cadastrar o mesmo callback no banco local e configurar as origens do portal/API | `apiUrl` pode ser loopback; o portal é escolhido pela API local |
109
+ | jogo em produção | cliente próprio + callback HTTPS exato | `https://api.pixelume.com.br` → `https://auth.pixelume.com.br` |
110
+
111
+ Execute `npm run example:prepare`, `npm run example:build` e depois
112
+ `npm --prefix examples/vite-basic run dev`. O exemplo tem botões de login, consulta e logout e é apenas
113
+ uma ferramenta de teste, não UI oficial.
114
+
115
+ ## Segurança e confiança
116
+
117
+ - O fluxo não é OIDC: não há ID Token, discovery, refresh token nem `client_secret`.
118
+ - A sessão central é cookie `HttpOnly`, `Secure`, `SameSite=Lax` e host-only da API. O jogo nunca a lê.
119
+ - Requests OAuth do jogo usam Bearer curto e `credentials: 'omit'`; `credentials: 'include'` pertence
120
+ apenas ao portal ao consultar/concluir a sessão central.
121
+ - `crypto.getRandomValues` e Web Crypto SHA-256 geram `state`, verifier e challenge; não há
122
+ `Math.random`.
123
+ - Access token, código e verifier não são registrados. Tokens não vão para `localStorage`, cookies ou URL.
124
+ - CORS/Origin não são considerados autenticação. PKCE, URI exata, state e vínculos persistidos pela API
125
+ são os controles de segurança.
126
+ - XSS ainda pode ler um token que esteja em memória; jogos devem manter CSP e dependências sob controle.
127
+
128
+ ## Erros
129
+
130
+ Todo erro lançado diretamente pelo SDK é `PixelumeError`. Os `kind` estáveis são `config`, `auth`,
131
+ `http`, `network`, `timeout`, `aborted` e `invalid_response`. `initialize()` captura falhas para permitir
132
+ o modo visitante; consulte `pixelume.auth.state.error`. Métodos chamados explicitamente (`login`,
133
+ `getPlayer`, `logout`) rejeitam normalmente.
134
+
135
+ O SDK não faz retry automático. Para 400/401/403/429/5xx, use `PixelumeHttpError.status`, `code` e
136
+ `requestId`; para cancelamento do chamador, use `AbortSignal`.
137
+
138
+ ## Health check
93
139
 
94
140
  ```ts
95
- import { isPixelumeError } from '@pixelume/sdk';
96
-
97
- const controller = new AbortController();
98
- try {
99
- await pixelume.health.check({ signal: controller.signal, timeoutMs: 5_000 });
100
- } catch (error) {
101
- if (!isPixelumeError(error)) throw error;
102
- switch (error.kind) {
103
- case 'http': // error.status, error.code ('RATE_LIMITED'…), error.requestId
104
- case 'network': // offline, DNS/TLS ou bloqueio de CORS
105
- case 'timeout': // error.timeoutMs
106
- case 'aborted': // cancelado pelo AbortSignal
107
- case 'invalid_response': // a API respondeu fora do contrato
108
- case 'config': // configuração inválida
109
- }
110
- }
141
+ const health = await pixelume.health.check();
111
142
  ```
112
143
 
113
- Mensagens de erro não incluem corpo, cookies, cabeçalhos nem query string. O corpo bruto fica em
114
- `PixelumeHttpError.payload`, propriedade não enumerável (não aparece em `JSON.stringify`). O SDK nunca
115
- escreve no console.
144
+ `GET /health` ainda não expõe CORS em produção, então funciona em Node/SSR/worker, mesma origem ou proxy
145
+ local, mas não diretamente de outro domínio no browser.
146
+
147
+ ## Compatibilidade
116
148
 
117
- ## Segurança
149
+ | SDK | Pixelume API | Pixelume Auth | Recursos |
150
+ | ------- | ---------------------------------------------------- | ------------------------------ | -------------------------------- |
151
+ | `0.2.x` | OpenAPI `0.4.0`, release SSO `3846479` ou compatível | Fase 2 `dd5a8e5` ou compatível | SSO PKCE, perfil, logout, health |
152
+ | `0.1.x` | API anterior/atual para `/health` | não consumido | fundação e health apenas |
118
153
 
119
- - O SDK não contém nem aceita API keys, tokens administrativos ou senhas.
120
- - `gameId` é público; autorização sempre vem da API.
121
- - Não há fluxo de login com senha dentro do jogo — e não haverá: o login será feito pelo Pixelume Auth.
122
- - O SDK não define `Origin`/`Referer`/`Cookie` e não trata CORS como segurança.
154
+ Como o pacote ainda está em `0.x`, mudanças incompatíveis usam incremento _minor_ e são listadas no
155
+ [CHANGELOG](CHANGELOG.md).
123
156
 
124
157
  ## Desenvolvimento
125
158
 
@@ -127,26 +160,14 @@ Requer Node.js 24 (`.nvmrc`).
127
160
 
128
161
  ```bash
129
162
  npm ci
130
- npm run lint # ESLint + Prettier
131
- npm run typecheck
132
- npm test # unitários + integração com servidor HTTP local (sem rede)
133
- npm run build # dist/ (ESM + .d.ts + sourcemaps)
134
- npm run check:package # dist browser-safe, conteúdo do tarball, publint, attw
135
- npm run example:prepare && npm run example:build # instala o .tgz no exemplo e compila
136
- npm run example:smoke # exemplo Node contra a API real (rede)
137
- npm run test:live # teste opcional contra a API real (rede)
138
- npm run verify # tudo o que a CI executa
163
+ npm run verify
164
+ npm run example:smoke # leitura opcional da API real
139
165
  ```
140
166
 
141
- ## Documentação
142
-
143
- - [Arquitetura e decisões técnicas](docs/architecture.md)
144
- - [Auditoria de contratos da API](docs/api-contract-audit.md)
145
- - [Publicação, CI/CD e rollback](docs/publishing.md)
146
- - [Roadmap](docs/roadmap.md)
147
- - [Changelog](CHANGELOG.md)
167
+ Mais detalhes: [integração SSO](docs/SSO_INTEGRATION.md),
168
+ [auditoria do contrato](docs/SSO_CONTRACT_AUDIT.md), [arquitetura](docs/architecture.md) e
169
+ [publicação](docs/publishing.md).
148
170
 
149
171
  ## Licença
150
172
 
151
- Proprietária — todos os direitos reservados. Veja [LICENSE](LICENSE). A disponibilidade pública no npm
152
- não concede direitos além dos descritos na licença.
173
+ Proprietária — todos os direitos reservados. Veja [LICENSE](LICENSE).
@@ -16,6 +16,23 @@ export type ParseResult<T> = {
16
16
  readonly reason: string;
17
17
  };
18
18
  export type BodyParser<T> = (body: unknown) => ParseResult<T>;
19
+ export interface PlayerWire {
20
+ id: string;
21
+ displayName: string;
22
+ avatarUrl: string | null;
23
+ }
24
+ export interface PlayerEnvelopeWire {
25
+ player: PlayerWire;
26
+ }
27
+ export declare const parsePlayerEnvelope: BodyParser<PlayerEnvelopeWire>;
28
+ export interface OAuthTokenResponseWire {
29
+ access_token: string;
30
+ token_type: 'Bearer';
31
+ expires_in: number;
32
+ scope: 'profile';
33
+ }
34
+ export declare const parseOAuthTokenResponse: BodyParser<OAuthTokenResponseWire>;
35
+ export declare const parseEmptyResponse: BodyParser<void>;
19
36
  /** `GET /health` → 200. OpenAPI: `{ status: 'ok', registration: enum, release?: /^[a-f0-9]{7,40}$/ }`. */
20
37
  export interface HealthResponseWire {
21
38
  status: 'ok';
@@ -13,6 +13,42 @@ const fail = (reason) => ({ ok: false, reason });
13
13
  function isRecord(value) {
14
14
  return typeof value === 'object' && value !== null && !Array.isArray(value);
15
15
  }
16
+ export const parsePlayerEnvelope = (body) => {
17
+ if (!isRecord(body) || !isRecord(body.player)) {
18
+ return fail('expected a JSON object containing "player"');
19
+ }
20
+ const { id, displayName, avatarUrl } = body.player;
21
+ if (typeof id !== 'string' || id.length === 0)
22
+ return fail('expected "player.id" to be a string');
23
+ if (typeof displayName !== 'string')
24
+ return fail('expected "player.displayName" to be a string');
25
+ if (avatarUrl !== null && typeof avatarUrl !== 'string') {
26
+ return fail('expected "player.avatarUrl" to be a string or null');
27
+ }
28
+ return ok({ player: { id, displayName, avatarUrl } });
29
+ };
30
+ export const parseOAuthTokenResponse = (body) => {
31
+ if (!isRecord(body))
32
+ return fail('expected a JSON object');
33
+ if (typeof body.access_token !== 'string' ||
34
+ !/^[A-Za-z0-9_-]{43,128}$/u.test(body.access_token)) {
35
+ return fail('expected "access_token" to be a base64url token');
36
+ }
37
+ if (body.token_type !== 'Bearer')
38
+ return fail('expected "token_type" to be "Bearer"');
39
+ if (!Number.isInteger(body.expires_in) || body.expires_in <= 0) {
40
+ return fail('expected "expires_in" to be a positive integer');
41
+ }
42
+ if (body.scope !== 'profile')
43
+ return fail('expected "scope" to be "profile"');
44
+ return ok({
45
+ access_token: body.access_token,
46
+ token_type: 'Bearer',
47
+ expires_in: body.expires_in,
48
+ scope: 'profile',
49
+ });
50
+ };
51
+ export const parseEmptyResponse = (body) => body === undefined ? ok(undefined) : fail('expected an empty response body');
16
52
  export const parseHealthResponse = (body) => {
17
53
  if (!isRecord(body))
18
54
  return fail('expected a JSON object');
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/contracts/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAOH,MAAM,EAAE,GAAG,CAAI,KAAQ,EAAkB,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AAClE,MAAM,IAAI,GAAG,CAAC,MAAc,EAAsB,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;AAE7E,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC;AASD,MAAM,CAAC,MAAM,mBAAmB,GAAmC,CAAC,IAAI,EAAE,EAAE;IAC1E,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC,wBAAwB,CAAC,CAAC;IAC3D,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC,8BAA8B,CAAC,CAAC;IACtE,IAAI,OAAO,IAAI,CAAC,YAAY,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC,wCAAwC,CAAC,CAAC;IACjG,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS,IAAI,OAAO,IAAI,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;QACnE,OAAO,IAAI,CAAC,gDAAgD,CAAC,CAAC;IAChE,CAAC;IACD,OAAO,EAAE,CAAC;QACR,MAAM,EAAE,IAAI;QACZ,YAAY,EAAE,IAAI,CAAC,YAAY;QAC/B,GAAG,CAAC,IAAI,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC;KACjE,CAAC,CAAC;AACL,CAAC,CAAC;AAOF,qGAAqG;AACrG,MAAM,UAAU,kBAAkB,CAAC,IAAa;IAC9C,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAC1D,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC;IAChD,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,SAAS,KAAK,QAAQ,EAAE,CAAC;QAC7F,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,CAAC;AACjD,CAAC","sourcesContent":["/**\n * Wire contracts of the Pixelume API used by the SDK, with hand-written runtime guards.\n *\n * Source of truth: `pixelume-api` (`docs/openapi.yaml` and `src/interfaces/http/app.ts`).\n * See `docs/api-contract-audit.md`. These types describe JSON on the wire and are internal;\n * public modules map them to SDK types so the API can evolve without breaking consumers.\n *\n * Validation is intentionally tolerant to *additive* changes (unknown fields are ignored,\n * unknown enum values are accepted as strings) and strict about the fields the SDK relies on.\n */\n\nexport type ParseResult<T> =\n { readonly ok: true; readonly value: T } | { readonly ok: false; readonly reason: string };\n\nexport type BodyParser<T> = (body: unknown) => ParseResult<T>;\n\nconst ok = <T>(value: T): ParseResult<T> => ({ ok: true, value });\nconst fail = (reason: string): ParseResult<never> => ({ ok: false, reason });\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/** `GET /health` → 200. OpenAPI: `{ status: 'ok', registration: enum, release?: /^[a-f0-9]{7,40}$/ }`. */\nexport interface HealthResponseWire {\n status: 'ok';\n registration: string;\n release?: string;\n}\n\nexport const parseHealthResponse: BodyParser<HealthResponseWire> = (body) => {\n if (!isRecord(body)) return fail('expected a JSON object');\n if (body.status !== 'ok') return fail('expected \"status\" to be \"ok\"');\n if (typeof body.registration !== 'string') return fail('expected \"registration\" to be a string');\n if (body.release !== undefined && typeof body.release !== 'string') {\n return fail('expected \"release\" to be a string when present');\n }\n return ok({\n status: 'ok',\n registration: body.registration,\n ...(body.release === undefined ? {} : { release: body.release }),\n });\n};\n\n/** Error envelope returned by every API error: `{ error: { code, message, requestId } }`. */\nexport interface ErrorEnvelopeWire {\n error: { code: string; message: string; requestId: string };\n}\n\n/** Returns the envelope when `body` matches it, `null` otherwise (e.g. an HTML 502 from a proxy). */\nexport function parseErrorEnvelope(body: unknown): ErrorEnvelopeWire | null {\n if (!isRecord(body) || !isRecord(body.error)) return null;\n const { code, message, requestId } = body.error;\n if (typeof code !== 'string' || typeof message !== 'string' || typeof requestId !== 'string') {\n return null;\n }\n return { error: { code, message, requestId } };\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/contracts/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Wire contracts of the Pixelume API used by the SDK, with hand-written runtime guards.\n *\n * Source of truth: `pixelume-api` (`docs/openapi.yaml` and `src/interfaces/http/app.ts`).\n * See `docs/api-contract-audit.md`. These types describe JSON on the wire and are internal;\n * public modules map them to SDK types so the API can evolve without breaking consumers.\n *\n * Validation is intentionally tolerant to *additive* changes (unknown fields are ignored,\n * unknown enum values are accepted as strings) and strict about the fields the SDK relies on.\n */\n\nexport type ParseResult<T> =\n { readonly ok: true; readonly value: T } | { readonly ok: false; readonly reason: string };\n\nexport type BodyParser<T> = (body: unknown) => ParseResult<T>;\n\nconst ok = <T>(value: T): ParseResult<T> => ({ ok: true, value });\nconst fail = (reason: string): ParseResult<never> => ({ ok: false, reason });\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nexport interface PlayerWire {\n id: string;\n displayName: string;\n avatarUrl: string | null;\n}\n\nexport interface PlayerEnvelopeWire {\n player: PlayerWire;\n}\n\nexport const parsePlayerEnvelope: BodyParser<PlayerEnvelopeWire> = (body) => {\n if (!isRecord(body) || !isRecord(body.player)) {\n return fail('expected a JSON object containing \"player\"');\n }\n const { id, displayName, avatarUrl } = body.player;\n if (typeof id !== 'string' || id.length === 0) return fail('expected \"player.id\" to be a string');\n if (typeof displayName !== 'string') return fail('expected \"player.displayName\" to be a string');\n if (avatarUrl !== null && typeof avatarUrl !== 'string') {\n return fail('expected \"player.avatarUrl\" to be a string or null');\n }\n return ok({ player: { id, displayName, avatarUrl } });\n};\n\nexport interface OAuthTokenResponseWire {\n access_token: string;\n token_type: 'Bearer';\n expires_in: number;\n scope: 'profile';\n}\n\nexport const parseOAuthTokenResponse: BodyParser<OAuthTokenResponseWire> = (body) => {\n if (!isRecord(body)) return fail('expected a JSON object');\n if (\n typeof body.access_token !== 'string' ||\n !/^[A-Za-z0-9_-]{43,128}$/u.test(body.access_token)\n ) {\n return fail('expected \"access_token\" to be a base64url token');\n }\n if (body.token_type !== 'Bearer') return fail('expected \"token_type\" to be \"Bearer\"');\n if (!Number.isInteger(body.expires_in) || (body.expires_in as number) <= 0) {\n return fail('expected \"expires_in\" to be a positive integer');\n }\n if (body.scope !== 'profile') return fail('expected \"scope\" to be \"profile\"');\n return ok({\n access_token: body.access_token,\n token_type: 'Bearer',\n expires_in: body.expires_in as number,\n scope: 'profile',\n });\n};\n\nexport const parseEmptyResponse: BodyParser<void> = (body) =>\n body === undefined ? ok(undefined) : fail('expected an empty response body');\n\n/** `GET /health` → 200. OpenAPI: `{ status: 'ok', registration: enum, release?: /^[a-f0-9]{7,40}$/ }`. */\nexport interface HealthResponseWire {\n status: 'ok';\n registration: string;\n release?: string;\n}\n\nexport const parseHealthResponse: BodyParser<HealthResponseWire> = (body) => {\n if (!isRecord(body)) return fail('expected a JSON object');\n if (body.status !== 'ok') return fail('expected \"status\" to be \"ok\"');\n if (typeof body.registration !== 'string') return fail('expected \"registration\" to be a string');\n if (body.release !== undefined && typeof body.release !== 'string') {\n return fail('expected \"release\" to be a string when present');\n }\n return ok({\n status: 'ok',\n registration: body.registration,\n ...(body.release === undefined ? {} : { release: body.release }),\n });\n};\n\n/** Error envelope returned by every API error: `{ error: { code, message, requestId } }`. */\nexport interface ErrorEnvelopeWire {\n error: { code: string; message: string; requestId: string };\n}\n\n/** Returns the envelope when `body` matches it, `null` otherwise (e.g. an HTML 502 from a proxy). */\nexport function parseErrorEnvelope(body: unknown): ErrorEnvelopeWire | null {\n if (!isRecord(body) || !isRecord(body.error)) return null;\n const { code, message, requestId } = body.error;\n if (typeof code !== 'string' || typeof message !== 'string' || typeof requestId !== 'string') {\n return null;\n }\n return { error: { code, message, requestId } };\n}\n"]}
@@ -1,3 +1,4 @@
1
+ import { type PixelumeAuthApi, type PixelumeAuthState } from '../modules/auth/AuthModule.js';
1
2
  import { type PixelumeHealthApi } from '../modules/health/HealthModule.js';
2
3
  import type { PixelumeClientConfig, PixelumeClientOptions } from './types.js';
3
4
  /**
@@ -14,7 +15,16 @@ import type { PixelumeClientConfig, PixelumeClientOptions } from './types.js';
14
15
  * @throws {PixelumeConfigError} when the options are invalid.
15
16
  */
16
17
  export declare class PixelumeClient {
18
+ #private;
17
19
  readonly config: PixelumeClientConfig;
18
20
  readonly health: PixelumeHealthApi;
21
+ readonly auth: PixelumeAuthApi;
19
22
  constructor(options?: PixelumeClientOptions);
23
+ /**
24
+ * Initializes browser authentication once. Callback failures become an anonymous state so a
25
+ * game can always continue as a visitor; inspect `pixelume.auth.state.error` for diagnostics.
26
+ */
27
+ initialize(): Promise<PixelumeAuthState>;
20
28
  }
29
+ /** Concise alias for new integrations; `PixelumeClient` remains supported. */
30
+ export { PixelumeClient as Pixelume };
@@ -1,4 +1,5 @@
1
1
  import { HttpClient } from '../http/HttpClient.js';
2
+ import { AuthModule, } from '../modules/auth/AuthModule.js';
2
3
  import { HealthModule } from '../modules/health/HealthModule.js';
3
4
  import { resolveConfig } from './config.js';
4
5
  /**
@@ -17,11 +18,17 @@ import { resolveConfig } from './config.js';
17
18
  export class PixelumeClient {
18
19
  config;
19
20
  health;
21
+ auth;
22
+ #authModule;
20
23
  constructor(options = {}) {
21
24
  const resolved = resolveConfig(options);
22
25
  this.config = Object.freeze({
23
26
  apiUrl: resolved.apiUrl,
24
27
  gameId: resolved.gameId,
28
+ clientId: resolved.clientId,
29
+ redirectUri: resolved.redirectUri,
30
+ authUrl: resolved.authUrl,
31
+ restoreSession: resolved.restoreSession,
25
32
  timeoutMs: resolved.timeoutMs,
26
33
  credentials: resolved.credentials,
27
34
  });
@@ -32,6 +39,17 @@ export class PixelumeClient {
32
39
  credentials: resolved.credentials,
33
40
  });
34
41
  this.health = new HealthModule(http);
42
+ this.#authModule = new AuthModule(http, resolved);
43
+ this.auth = this.#authModule;
44
+ }
45
+ /**
46
+ * Initializes browser authentication once. Callback failures become an anonymous state so a
47
+ * game can always continue as a visitor; inspect `pixelume.auth.state.error` for diagnostics.
48
+ */
49
+ initialize() {
50
+ return this.#authModule.initialize();
35
51
  }
36
52
  }
53
+ /** Concise alias for new integrations; `PixelumeClient` remains supported. */
54
+ export { PixelumeClient as Pixelume };
37
55
  //# sourceMappingURL=PixelumeClient.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"PixelumeClient.js","sourceRoot":"","sources":["../../src/core/PixelumeClient.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EAAE,YAAY,EAA0B,MAAM,mCAAmC,CAAC;AACzF,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAG5C;;;;;;;;;;;;GAYG;AACH,MAAM,OAAO,cAAc;IAChB,MAAM,CAAuB;IAC7B,MAAM,CAAoB;IAEnC,YAAY,UAAiC,EAAE;QAC7C,MAAM,QAAQ,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;QACxC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;YAC1B,MAAM,EAAE,QAAQ,CAAC,MAAM;YACvB,MAAM,EAAE,QAAQ,CAAC,MAAM;YACvB,SAAS,EAAE,QAAQ,CAAC,SAAS;YAC7B,WAAW,EAAE,QAAQ,CAAC,WAAW;SAClC,CAAC,CAAC;QACH,MAAM,IAAI,GAAG,IAAI,UAAU,CAAC;YAC1B,OAAO,EAAE,QAAQ,CAAC,MAAM;YACxB,KAAK,EAAE,QAAQ,CAAC,KAAK;YACrB,SAAS,EAAE,QAAQ,CAAC,SAAS;YAC7B,WAAW,EAAE,QAAQ,CAAC,WAAW;SAClC,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,GAAG,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC;IACvC,CAAC;CACF","sourcesContent":["import { HttpClient } from '../http/HttpClient.js';\nimport { HealthModule, type PixelumeHealthApi } from '../modules/health/HealthModule.js';\nimport { resolveConfig } from './config.js';\nimport type { PixelumeClientConfig, PixelumeClientOptions } from './types.js';\n\n/**\n * Entry point of the SDK. Create one instance per API/game and share it.\n *\n * ```ts\n * const pixelume = new PixelumeClient({ gameId: 'rail-puzzle' });\n * const health = await pixelume.health.check();\n * ```\n *\n * Feature modules are exposed as read-only properties. Phase 1 ships only `health`; future modules\n * (authentication, saves…) will be added as new properties without changing existing ones.\n *\n * @throws {PixelumeConfigError} when the options are invalid.\n */\nexport class PixelumeClient {\n readonly config: PixelumeClientConfig;\n readonly health: PixelumeHealthApi;\n\n constructor(options: PixelumeClientOptions = {}) {\n const resolved = resolveConfig(options);\n this.config = Object.freeze({\n apiUrl: resolved.apiUrl,\n gameId: resolved.gameId,\n timeoutMs: resolved.timeoutMs,\n credentials: resolved.credentials,\n });\n const http = new HttpClient({\n baseUrl: resolved.apiUrl,\n fetch: resolved.fetch,\n timeoutMs: resolved.timeoutMs,\n credentials: resolved.credentials,\n });\n this.health = new HealthModule(http);\n }\n}\n"]}
1
+ {"version":3,"file":"PixelumeClient.js","sourceRoot":"","sources":["../../src/core/PixelumeClient.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EACL,UAAU,GAGX,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,YAAY,EAA0B,MAAM,mCAAmC,CAAC;AACzF,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAG5C;;;;;;;;;;;;GAYG;AACH,MAAM,OAAO,cAAc;IAChB,MAAM,CAAuB;IAC7B,MAAM,CAAoB;IAC1B,IAAI,CAAkB;IACtB,WAAW,CAAa;IAEjC,YAAY,UAAiC,EAAE;QAC7C,MAAM,QAAQ,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;QACxC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;YAC1B,MAAM,EAAE,QAAQ,CAAC,MAAM;YACvB,MAAM,EAAE,QAAQ,CAAC,MAAM;YACvB,QAAQ,EAAE,QAAQ,CAAC,QAAQ;YAC3B,WAAW,EAAE,QAAQ,CAAC,WAAW;YACjC,OAAO,EAAE,QAAQ,CAAC,OAAO;YACzB,cAAc,EAAE,QAAQ,CAAC,cAAc;YACvC,SAAS,EAAE,QAAQ,CAAC,SAAS;YAC7B,WAAW,EAAE,QAAQ,CAAC,WAAW;SAClC,CAAC,CAAC;QACH,MAAM,IAAI,GAAG,IAAI,UAAU,CAAC;YAC1B,OAAO,EAAE,QAAQ,CAAC,MAAM;YACxB,KAAK,EAAE,QAAQ,CAAC,KAAK;YACrB,SAAS,EAAE,QAAQ,CAAC,SAAS;YAC7B,WAAW,EAAE,QAAQ,CAAC,WAAW;SAClC,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,GAAG,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC;QACrC,IAAI,CAAC,WAAW,GAAG,IAAI,UAAU,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QAClD,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC;IAC/B,CAAC;IAED;;;OAGG;IACH,UAAU;QACR,OAAO,IAAI,CAAC,WAAW,CAAC,UAAU,EAAE,CAAC;IACvC,CAAC;CACF;AAED,8EAA8E;AAC9E,OAAO,EAAE,cAAc,IAAI,QAAQ,EAAE,CAAC","sourcesContent":["import { HttpClient } from '../http/HttpClient.js';\nimport {\n AuthModule,\n type PixelumeAuthApi,\n type PixelumeAuthState,\n} from '../modules/auth/AuthModule.js';\nimport { HealthModule, type PixelumeHealthApi } from '../modules/health/HealthModule.js';\nimport { resolveConfig } from './config.js';\nimport type { PixelumeClientConfig, PixelumeClientOptions } from './types.js';\n\n/**\n * Entry point of the SDK. Create one instance per API/game and share it.\n *\n * ```ts\n * const pixelume = new PixelumeClient({ gameId: 'rail-puzzle' });\n * const health = await pixelume.health.check();\n * ```\n *\n * Feature modules are exposed as read-only properties. Phase 1 ships only `health`; future modules\n * (authentication, saves…) will be added as new properties without changing existing ones.\n *\n * @throws {PixelumeConfigError} when the options are invalid.\n */\nexport class PixelumeClient {\n readonly config: PixelumeClientConfig;\n readonly health: PixelumeHealthApi;\n readonly auth: PixelumeAuthApi;\n readonly #authModule: AuthModule;\n\n constructor(options: PixelumeClientOptions = {}) {\n const resolved = resolveConfig(options);\n this.config = Object.freeze({\n apiUrl: resolved.apiUrl,\n gameId: resolved.gameId,\n clientId: resolved.clientId,\n redirectUri: resolved.redirectUri,\n authUrl: resolved.authUrl,\n restoreSession: resolved.restoreSession,\n timeoutMs: resolved.timeoutMs,\n credentials: resolved.credentials,\n });\n const http = new HttpClient({\n baseUrl: resolved.apiUrl,\n fetch: resolved.fetch,\n timeoutMs: resolved.timeoutMs,\n credentials: resolved.credentials,\n });\n this.health = new HealthModule(http);\n this.#authModule = new AuthModule(http, resolved);\n this.auth = this.#authModule;\n }\n\n /**\n * Initializes browser authentication once. Callback failures become an anonymous state so a\n * game can always continue as a visitor; inspect `pixelume.auth.state.error` for diagnostics.\n */\n initialize(): Promise<PixelumeAuthState> {\n return this.#authModule.initialize();\n }\n}\n\n/** Concise alias for new integrations; `PixelumeClient` remains supported. */\nexport { PixelumeClient as Pixelume };\n"]}
@@ -1,10 +1,17 @@
1
1
  import type { CredentialsPolicy, FetchLike } from '../http/types.js';
2
2
  import type { PixelumeClientOptions } from './types.js';
3
3
  export declare const DEFAULT_API_URL = "https://api.pixelume.com.br";
4
+ export declare const DEFAULT_AUTH_URL = "https://auth.pixelume.com.br";
4
5
  export declare const DEFAULT_TIMEOUT_MS = 10000;
5
6
  export interface ResolvedConfig {
6
7
  apiUrl: string;
7
8
  gameId: string | null;
9
+ clientId: string | null;
10
+ redirectUri: string | null;
11
+ authUrl: string;
12
+ restoreSession: boolean;
13
+ allowInsecureHttp: boolean;
14
+ browser: PixelumeClientOptions['browser'];
8
15
  timeoutMs: number;
9
16
  credentials: CredentialsPolicy;
10
17
  fetch: FetchLike;
@@ -15,11 +22,13 @@ export declare function resolveConfig(options: PixelumeClientOptions): ResolvedC
15
22
  * `[::1]`). Other `http:` hosts (e.g. a LAN IP while testing on a phone) need `allowInsecureHttp`.
16
23
  */
17
24
  export declare function normalizeApiUrl(value: string, allowInsecureHttp: boolean): string;
25
+ export declare function normalizeRedirectUri(value: string, allowInsecureHttp: boolean): string;
18
26
  /**
19
27
  * Game identifiers are public, non-secret slugs (e.g. `rail-puzzle`): lowercase letters, digits
20
28
  * and inner hyphens, 1–64 characters. They identify a game, they never authorize anything.
21
29
  */
22
30
  export declare function validateGameId(value: string): string;
31
+ export declare function validateClientId(value: string): string;
23
32
  /** For feature modules that need a game context (future phases). */
24
33
  export declare function requireGameId(config: Pick<ResolvedConfig, 'gameId'>, feature: string): string;
25
34
  export declare function validateTimeout(value: number): number;