@pixelume/sdk 0.1.1 → 0.3.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,42 @@ conter mudanças incompatíveis, sempre listadas em **Changed/Removed**.
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.3.0] - 2026-10-13
11
+
12
+ ### Added
13
+
14
+ - Entry point opt-in `@pixelume/sdk/ui` com `mountPixelumeWidget()`, Shadow DOM aberto e controlador
15
+ `hide()`, `show()` e `destroy()`.
16
+ - Widget oficial responsivo com estados de visitante, carregamento, autenticado e erro; login/logout
17
+ reutilizam exclusivamente `client.auth`.
18
+ - Posição, distância das bordas, container e `z-index` configuráveis, incluindo safe areas mobile.
19
+ - Navegação por teclado, Escape com devolução de foco, foco visível, região de status e isolamento de
20
+ eventos pointer/touch/teclado do jogo.
21
+ - Testes DOM do widget e validação dos dois entrypoints no consumidor empacotado.
22
+
23
+ ### Changed
24
+
25
+ - O exemplo Vite agora monta também o widget oficial, mantendo os controles headless para demonstrar os
26
+ dois modos de consumo.
27
+
28
+ ## [0.2.0] - 2026-10-13
29
+
30
+ ### Added
31
+
32
+ - `Pixelume` como alias conciso e `PixelumeClient.initialize()` idempotente.
33
+ - `client.auth` com Authorization Code + PKCE S256: login por redirecionamento, callback validado,
34
+ consulta de jogador, revogação/logout, estado observável e assinatura cancelável.
35
+ - Restauração após recarga por nova autorização SSO, sem persistir access token; somente um marcador
36
+ não sensível e o estado PKCE transitório ficam em `sessionStorage`.
37
+ - Configuração tipada de `clientId`, `redirectUri`, `authUrl`, `restoreSession` e adaptador de browser.
38
+ - Exemplo Vite com fluxo de autenticação completo e auditoria/documentação do contrato real.
39
+
40
+ ### Changed
41
+
42
+ - O `kind` de `PixelumeError` ganhou o valor aditivo `auth`; consumidores `0.x` com `switch`
43
+ exaustivo devem tratar esse novo caso.
44
+ - O cliente HTTP interno passou a serializar formulários OAuth URL-encoded.
45
+
10
46
  ## [0.1.1] - 2026-10-13
11
47
 
12
48
  ### Changed
@@ -29,6 +65,8 @@ conter mudanças incompatíveis, sempre listadas em **Changed/Removed**.
29
65
  `invalid_response`) e `isPixelumeError()`.
30
66
  - Build ESM + `.d.ts`, CI e workflow de publicação via npm Trusted Publishing.
31
67
 
32
- [Unreleased]: https://github.com/ericksonnascimento/pixelume-sdk/compare/v0.1.1...HEAD
68
+ [Unreleased]: https://github.com/ericksonnascimento/pixelume-sdk/compare/v0.3.0...HEAD
69
+ [0.3.0]: https://github.com/ericksonnascimento/pixelume-sdk/compare/v0.2.0...v0.3.0
70
+ [0.2.0]: https://github.com/ericksonnascimento/pixelume-sdk/compare/v0.1.1...v0.2.0
33
71
  [0.1.1]: https://github.com/ericksonnascimento/pixelume-sdk/compare/v0.1.0...v0.1.1
34
72
  [0.1.0]: https://github.com/ericksonnascimento/pixelume-sdk/releases/tag/v0.1.0
package/README.md CHANGED
@@ -1,27 +1,9 @@
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 para jogos web da Pixelume. O core headless continua sendo o padrão; a versão 0.3 adiciona
4
+ um widget oficial opt-in em `@pixelume/sdk/ui`. Os dois modos usam a mesma autenticação centralizada com
5
+ OAuth 2.0 Authorization Code + PKCE S256. Não há dependências de runtime, senha no jogo nem
6
+ `client_secret`.
25
7
 
26
8
  ## Instalação
27
9
 
@@ -29,91 +11,185 @@ engine (funciona com Vite, Three.js, React, JavaScript puro, SSR e workers).
29
11
  npm install @pixelume/sdk
30
12
  ```
31
13
 
32
- Publicado no npm: [`@pixelume/sdk`](https://www.npmjs.com/package/@pixelume/sdk) (versão atual `0.1.0`).
14
+ O pacote é ESM-only, inclui tipos e funciona em Vite, JavaScript/TypeScript puro e engines web. Em
15
+ CommonJS, use `await import('@pixelume/sdk')`.
16
+
17
+ ## Configuração obrigatória no backend
18
+
19
+ Antes de integrar um jogo, registre na Pixelume API:
33
20
 
34
- ## Uso
21
+ - um `client_id` público, normalmente igual ao `gameId`;
22
+ - o escopo `profile`;
23
+ - cada callback completo por igualdade exata de scheme, host, porta, path e query.
24
+
25
+ Não são aceitos curingas, prefixos ou callbacks enviados dinamicamente. O cliente local já cadastrado é
26
+ `pixelume-local-dev`, exclusivamente com `http://localhost:4173/oauth/callback` e
27
+ `http://127.0.0.1:4173/oauth/callback`. Jogos publicados exigem migration/configuração revisada na API.
28
+
29
+ ## Uso headless
35
30
 
36
31
  ```ts
37
- import { PixelumeClient } from '@pixelume/sdk';
32
+ import { Pixelume, isPixelumeError } from '@pixelume/sdk';
33
+
34
+ const pixelume = new Pixelume({
35
+ gameId: 'rail-puzzle',
36
+ // Use clientId somente quando for diferente do gameId.
37
+ // clientId: 'rail-puzzle.web',
38
+ // Padrão no browser: `${location.origin}/oauth/callback`.
39
+ redirectUri: 'https://rail-puzzle.pixelume.com.br/oauth/callback',
40
+ });
38
41
 
39
- const pixelume = new PixelumeClient({
40
- apiUrl: 'https://api.pixelume.com.br', // padrão
41
- gameId: 'rail-puzzle', // opcional nesta fase
42
+ const initial = await pixelume.initialize();
43
+ // `initialize()` nunca impede o jogo de abrir: falhas de callback viram estado anonymous/error.
44
+
45
+ const unsubscribe = pixelume.auth.subscribe((state, previous) => {
46
+ if (state.status !== previous.status) renderAccountState(state);
42
47
  });
43
48
 
44
- const health = await pixelume.health.check();
45
- // { status: 'ok', registration: 'email-verification', release: 'd99d195…' }
46
- ```
49
+ const player = await pixelume.auth.getPlayer(); // PixelumePlayer | null
47
50
 
48
- Saída real obtida pelo exemplo em 2026-10-13 (Node.js, pacote instalado via `npm pack`):
51
+ loginButton.addEventListener('click', () => {
52
+ void pixelume.auth.login(); // navegação de topo para a API e o portal Auth
53
+ });
49
54
 
50
- ```json
51
- {
52
- "sdk": "0.1.0",
53
- "apiUrl": "https://api.pixelume.com.br",
54
- "health": {
55
- "status": "ok",
56
- "registration": "email-verification",
57
- "release": "d99d19543620f09309268f67d959e874ddc78ecf"
55
+ logoutButton.addEventListener('click', async () => {
56
+ try {
57
+ await pixelume.auth.logout(); // revoga somente o token deste jogo
58
+ } catch (error) {
59
+ if (!isPixelumeError(error)) throw error;
60
+ showRecoverableError(error.kind);
58
61
  }
59
- }
62
+ });
63
+
64
+ // Ao desmontar a tela:
65
+ unsubscribe();
60
66
  ```
61
67
 
62
- ### `health.check()` no navegador
68
+ Importar ou instanciar o core não cria elementos, não injeta estilos e não ativa UI. O health check
69
+ também é totalmente independente: chamar `/health` nunca monta o widget.
63
70
 
64
- A API ainda **não envia cabeçalhos CORS em `/health`** (só em `/v1/*`). Em uma página servida por outra
65
- origem, o navegador bloqueia a resposta e o SDK rejeita com `PixelumeNetworkError`. Use `health.check()`
66
- no servidor (Node/SSR/workers), em ferramentas, ou na mesma origem via proxy — o
67
- [exemplo Vite](examples/vite-basic) usa o proxy do `vite dev`. A correção depende da API
68
- ([auditoria](docs/api-contract-audit.md)).
71
+ ## Widget opcional
69
72
 
70
- ### Configuração
73
+ ```ts
74
+ import { Pixelume } from '@pixelume/sdk';
75
+ import { mountPixelumeWidget } from '@pixelume/sdk/ui';
71
76
 
72
- | Opção | Padrão | Descrição |
73
- | ------------------- | ----------------------------- | -------------------------------------------------------------------------------------------- |
74
- | `apiUrl` | `https://api.pixelume.com.br` | `https:` obrigatório; `http:` aceito só para `localhost`/`127.0.0.1`/`[::1]`/`*.localhost`. |
75
- | `gameId` | — | slug público (`a-z`, `0-9`, `-`, até 64). Identifica o jogo; **não** é credencial. |
76
- | `timeoutMs` | `10000` | tempo máximo de cada operação, incluindo leitura do corpo. |
77
- | `credentials` | `'omit'` | política de cookies do `fetch` para módulos futuros. O SDK não presume cookies cross-origin. |
78
- | `fetch` | `globalThis.fetch` | implementação injetável (testes, SSR, instrumentação). |
79
- | `allowInsecureHttp` | `false` | permite `http:` em hosts não-loopback (ex.: IP da LAN). Nunca em produção. |
77
+ const pixelume = new Pixelume({
78
+ gameId: 'rail-puzzle',
79
+ redirectUri: 'https://rail-puzzle.pixelume.com.br/oauth/callback',
80
+ });
80
81
 
81
- Opções inválidas lançam `PixelumeConfigError` no construtor. Cada operação aceita
82
- `{ signal?: AbortSignal; timeoutMs?: number }`.
82
+ await pixelume.initialize();
83
83
 
84
- ### Erros
84
+ const widget = mountPixelumeWidget(pixelume, {
85
+ position: 'bottom-right',
86
+ offset: 16,
87
+ theme: 'pixel',
88
+ });
85
89
 
86
- Todas as falhas são `PixelumeError` com um `kind` estável:
90
+ // Ao esconder controles, pausar ou trocar de tela:
91
+ widget.hide();
92
+ widget.show();
93
+
94
+ // Ao encerrar a tela/jogo:
95
+ widget.destroy();
96
+ ```
97
+
98
+ O painel usa Shadow DOM aberto, não aplica CSS global e chama somente `pixelume.auth.login()` e
99
+ `pixelume.auth.logout()`. Entrar sempre redireciona ao portal Pixelume Auth; o widget não exibe campos de
100
+ e-mail ou senha. Para overlays próprios ou canvas fullscreen, passe `container`, ajuste `position`,
101
+ `offset` e `zIndex`. Veja o [guia completo do widget](docs/phase-03-widget.md).
102
+
103
+ `pixelume.auth.state.status` é `uninitialized`, `redirecting`, `exchanging`, `anonymous` ou
104
+ `authenticated`. O estado anônimo informa `reason` (`visitor`, `cancelled`, `expired`, `logged_out` ou
105
+ `error`) e, em falha, um `PixelumeError`. Cancelar no portal resulta em visitante, não em exceção.
106
+
107
+ ### Visitante, recarga e expiração
108
+
109
+ - Sem login ou callback, `initialize()` termina como visitante e não faz requisição.
110
+ - O access token opaco dura 10 minutos e fica **somente em memória**.
111
+ - Após autenticar, o SDK guarda em `sessionStorage` apenas o marcador não secreto de que aquela aba
112
+ tinha uma sessão. Em uma recarga, inicia uma nova autorização; enquanto a sessão central da API
113
+ continuar válida, o portal retorna sem pedir senha. `restoreSession: false` desliga esse comportamento.
114
+ - PKCE `state`/`code_verifier` ficam na sessão por no máximo 10 minutos e são removidos antes da troca.
115
+ - Código expirado/repetido, `state` divergente, callback adulterado e 401 deixam o jogo utilizável como
116
+ visitante. O jogo pode oferecer o login novamente.
117
+ - `logout()` limpa o estado local imediatamente e chama `/v1/oauth/revoke`. A sessão central do portal
118
+ continua existente por desenho; entrar novamente pode ser imediato.
119
+
120
+ ## Configuração
121
+
122
+ | Opção | Padrão | Observação |
123
+ | ---------------- | ------------------------------------ | ----------------------------------------------------------------------------------------- |
124
+ | `apiUrl` | `https://api.pixelume.com.br` | HTTPS; HTTP só em loopback ou com `allowInsecureHttp` explícito |
125
+ | `authUrl` | `https://auth.pixelume.com.br` | Portal esperado; a API continua sendo a autoridade que escolhe o redirect |
126
+ | `gameId` | — | slug público do jogo; também é o `clientId` padrão |
127
+ | `clientId` | `gameId` | identificador OAuth público cadastrado; nunca é credencial |
128
+ | `redirectUri` | `<origin>/oauth/callback` no browser | deve coincidir exatamente com o cadastro e não pode conter fragmento nem parâmetros OAuth |
129
+ | `restoreSession` | `true` | reautoriza após reload da mesma aba sem persistir token |
130
+ | `timeoutMs` | `10000` | cobre resposta e leitura do corpo |
131
+ | `credentials` | `omit` | política dos módulos genéricos; Auth força `omit` para token/userinfo/revoke |
132
+ | `fetch` | `globalThis.fetch` | injetável para testes/instrumentação |
133
+ | `browser` | globals do browser | adaptador avançado para webviews e testes |
134
+
135
+ `authUrl` é informativo/configurável para ambientes, mas o SDK nunca concatena nem escolhe um retorno
136
+ via portal: `/v1/oauth/authorize` valida o cliente/callback e redireciona para o portal configurado na
137
+ API. Isso evita transformar o SDK em fonte de open redirect.
138
+
139
+ ## Desenvolvimento local e produção
140
+
141
+ | Ambiente | Cliente/callback | API/portal |
142
+ | ---------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------- |
143
+ | exemplo local | `pixelume-local-dev` + `http://localhost:4173/oauth/callback` | produção por padrão; callbacks loopback já cadastrados |
144
+ | 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 |
145
+ | jogo em produção | cliente próprio + callback HTTPS exato | `https://api.pixelume.com.br` → `https://auth.pixelume.com.br` |
146
+
147
+ Execute `npm run example:prepare`, `npm run example:build` e depois
148
+ `npm --prefix examples/vite-basic run dev`. O exemplo tem botões de login, consulta e logout e é apenas
149
+ uma ferramenta de teste, não UI oficial.
150
+
151
+ ## Segurança e confiança
152
+
153
+ - O fluxo não é OIDC: não há ID Token, discovery, refresh token nem `client_secret`.
154
+ - A sessão central é cookie `HttpOnly`, `Secure`, `SameSite=Lax` e host-only da API. O jogo nunca a lê.
155
+ - Requests OAuth do jogo usam Bearer curto e `credentials: 'omit'`; `credentials: 'include'` pertence
156
+ apenas ao portal ao consultar/concluir a sessão central.
157
+ - `crypto.getRandomValues` e Web Crypto SHA-256 geram `state`, verifier e challenge; não há
158
+ `Math.random`.
159
+ - Access token, código e verifier não são registrados. Tokens não vão para `localStorage`, cookies ou URL.
160
+ - CORS/Origin não são considerados autenticação. PKCE, URI exata, state e vínculos persistidos pela API
161
+ são os controles de segurança.
162
+ - XSS ainda pode ler um token que esteja em memória; jogos devem manter CSP e dependências sob controle.
163
+
164
+ ## Erros
165
+
166
+ Todo erro lançado diretamente pelo SDK é `PixelumeError`. Os `kind` estáveis são `config`, `auth`,
167
+ `http`, `network`, `timeout`, `aborted` e `invalid_response`. `initialize()` captura falhas para permitir
168
+ o modo visitante; consulte `pixelume.auth.state.error`. Métodos chamados explicitamente (`login`,
169
+ `getPlayer`, `logout`) rejeitam normalmente.
170
+
171
+ O SDK não faz retry automático. Para 400/401/403/429/5xx, use `PixelumeHttpError.status`, `code` e
172
+ `requestId`; para cancelamento do chamador, use `AbortSignal`.
173
+
174
+ ## Health check
87
175
 
88
176
  ```ts
89
- import { isPixelumeError } from '@pixelume/sdk';
90
-
91
- const controller = new AbortController();
92
- try {
93
- await pixelume.health.check({ signal: controller.signal, timeoutMs: 5_000 });
94
- } catch (error) {
95
- if (!isPixelumeError(error)) throw error;
96
- switch (error.kind) {
97
- case 'http': // error.status, error.code ('RATE_LIMITED'…), error.requestId
98
- case 'network': // offline, DNS/TLS ou bloqueio de CORS
99
- case 'timeout': // error.timeoutMs
100
- case 'aborted': // cancelado pelo AbortSignal
101
- case 'invalid_response': // a API respondeu fora do contrato
102
- case 'config': // configuração inválida
103
- }
104
- }
177
+ const health = await pixelume.health.check();
105
178
  ```
106
179
 
107
- Mensagens de erro não incluem corpo, cookies, cabeçalhos nem query string. O corpo bruto fica em
108
- `PixelumeHttpError.payload`, propriedade não enumerável (não aparece em `JSON.stringify`). O SDK nunca
109
- escreve no console.
180
+ `GET /health` ainda não expõe CORS em produção, então funciona em Node/SSR/worker, mesma origem ou proxy
181
+ local, mas não diretamente de outro domínio no browser.
182
+
183
+ ## Compatibilidade
110
184
 
111
- ## Segurança
185
+ | SDK | Pixelume API | Pixelume Auth | Recursos |
186
+ | ------- | ---------------------------------------------------- | ------------------------------ | ----------------------------------------------- |
187
+ | `0.3.x` | OpenAPI `0.4.0`, release SSO `3846479` ou compatível | Fase 2 `3a1ded4` ou compatível | Widget opt-in, SSO PKCE, perfil, logout, health |
188
+ | `0.2.x` | OpenAPI `0.4.0`, release SSO `3846479` ou compatível | Fase 2 `3a1ded4` ou compatível | SSO PKCE, perfil, logout, health |
189
+ | `0.1.x` | API anterior/atual para `/health` | não consumido | fundação e health apenas |
112
190
 
113
- - O SDK não contém nem aceita API keys, tokens administrativos ou senhas.
114
- - `gameId` é público; autorização sempre vem da API.
115
- - Não há fluxo de login com senha dentro do jogo — e não haverá: o login será feito pelo Pixelume Auth.
116
- - O SDK não define `Origin`/`Referer`/`Cookie` e não trata CORS como segurança.
191
+ Como o pacote ainda está em `0.x`, mudanças incompatíveis usam incremento _minor_ e são listadas no
192
+ [CHANGELOG](CHANGELOG.md).
117
193
 
118
194
  ## Desenvolvimento
119
195
 
@@ -121,26 +197,14 @@ Requer Node.js 24 (`.nvmrc`).
121
197
 
122
198
  ```bash
123
199
  npm ci
124
- npm run lint # ESLint + Prettier
125
- npm run typecheck
126
- npm test # unitários + integração com servidor HTTP local (sem rede)
127
- npm run build # dist/ (ESM + .d.ts + sourcemaps)
128
- npm run check:package # dist browser-safe, conteúdo do tarball, publint, attw
129
- npm run example:prepare && npm run example:build # instala o .tgz no exemplo e compila
130
- npm run example:smoke # exemplo Node contra a API real (rede)
131
- npm run test:live # teste opcional contra a API real (rede)
132
- npm run verify # tudo o que a CI executa
200
+ npm run verify
201
+ npm run example:smoke # leitura opcional da API real
133
202
  ```
134
203
 
135
- ## Documentação
136
-
137
- - [Arquitetura e decisões técnicas](docs/architecture.md)
138
- - [Auditoria de contratos da API](docs/api-contract-audit.md)
139
- - [Publicação, CI/CD e rollback](docs/publishing.md)
140
- - [Roadmap](docs/roadmap.md)
141
- - [Changelog](CHANGELOG.md)
204
+ Mais detalhes: [widget oficial](docs/phase-03-widget.md), [integração SSO](docs/SSO_INTEGRATION.md),
205
+ [auditoria do contrato](docs/SSO_CONTRACT_AUDIT.md), [arquitetura](docs/architecture.md) e
206
+ [publicação](docs/publishing.md).
142
207
 
143
208
  ## Licença
144
209
 
145
- Proprietária — todos os direitos reservados. Veja [LICENSE](LICENSE). A disponibilidade pública no npm
146
- não concede direitos além dos descritos na licença.
210
+ 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;