# Vimine (navegador)

> Referência da classe Vimine do @vimine/sdk — login, sessão, chamadas à API, storage.

URL: https://developers.vimine.network/pt/sdk/browser/

```js
import { Vimine } from '@vimine/sdk';
const vimine = new Vimine(options);
```

`Vimine` cuida do **Entrar com Vimine** (OAuth 2.1 + PKCE), da sessão do associado e das chamadas à
API do associado. Uma instância por cliente; crie-a uma vez e reutilize-a.

## Opções

| Opção | Tipo | Padrão | |
|---|---|---|---|
| `clientId` | `string` | — | o seu `vmc_…` (obrigatório) |
| `redirectUri` | `string` | — | a página que chama `handleRedirect()`, registrada para o cliente (obrigatório) |
| `scopes` | `Scope[]` | `['profile:read', 'membership:read']` | permissões pedidas por `signIn()` |
| `storage` | `'session' \| 'local' \| 'memory' \| TokenStorage` | `'session'` | onde ficam a sessão e o login pendente |
| `storageKey` | `string` | `vimine:<clientId>:` | prefixo das chaves no storage |
| `locale` | `Locale` | `'it'` | idioma das páginas de login e de consentimento |
| `apiUrl` | `string` | `https://api.vimine.network` | origem da API |
| `appUrl` | `string` | `https://app.vimine.network` | origem das páginas de login |
| `fetch` | `typeof fetch` | `globalThis.fetch` | para testes e polyfills |
| `navigate` | `(url) => void` | `top.location.assign` | como sair da página |

O construtor lança um `VimineError` (`invalid_options`) sem `clientId` ou `redirectUri`.

## Login

### `signIn(options?)`

Manda a **janela principal** para a página de login e de consentimento. Chame-o a partir de um gesto do usuário (um
clique). De dentro de um iframe de outra origem a navegação é recusada (`framed`): a página de login nunca é mostrada
num frame.

| Opção | |
|---|---|
| `scopes` | permissões deste login |
| `prompt` | `'login'` (credenciais sempre) ou `'consent'` (consentimento sempre) |
| `returnTo` | um estado seu, guardado localmente e devolvido por `handleRedirect()` — ele não é enviado |

### `createSignInUrl(options?)`

Monta a URL de autorização e guarda o login pendente (verificador PKCE e `state`) sem
navegar. Útil quando você mesmo renderiza o link.

### `hasRedirectParams(url?)`

`true` quando a URL (padrão: a página atual) traz os parâmetros de um retorno (`code`+`state` ou `error`).

### `handleRedirect({ url?, cleanUrl? })`

Na página de callback: confere o `state`, troca o código por tokens e guarda a sessão. Devolve
`{ session, returnTo }`, ou `null` quando a URL não traz parâmetros de login. Com `cleanUrl` (padrão
`true`) ele remove `code`, `state` e `error` da barra de endereços.

Erros (`VimineError.code`): `access_denied` e os outros erros OAuth quando o associado não autorizou;
`no_flow` quando nenhum login foi iniciado neste navegador (storage vazio ou expirado); `state_mismatch` quando o
retorno não corresponde; `invalid_grant` quando o código expirou ou já foi usado.

## Sessão

### `getSession()` · `isSignedIn()`

A sessão guardada, ou `null`.

```ts
interface Session {
    accessToken: string;
    refreshToken: string | null;
    expiresAt: number;            // ms, absoluto
    scope: Scope[];
    sub: string | null;           // o identificador do associado para a sua associação (vms_…)
    mode?: 'live' | 'test';       // 'test' para as sessões de um cliente de teste
}
```

### `getAccessToken()`

Um access token válido, renovado quando vence em menos de 30 segundos. Lança `not_signed_in` sem uma sessão.

### `refresh()`

Renova a sessão com o refresh token (uma única requisição mesmo quando chamado de forma concorrente). Quando o refresh
token não pode mais ser usado, a sessão é apagada e o erro é relançado.

### `signOut({ everywhere?, postLogoutRedirectUri?, state? })`

Revoga o refresh token (RFC 7009, na medida do possível) e esquece a sessão. Com `everywhere: true`, depois manda
o navegador para o `/oauth/logout` do app Vimine, que encerra a sessão do associado e volta para
`postLogoutRedirectUri` (padrão `redirectUri`; precisa estar numa origem registrada).

### `onChange(listener)`

`listener(session | null)` a cada login, renovação e logout; com `storage: 'local'`, também para os que
aconteceram em outras abas. Devolve a função que remove o listener.

## API do associado

### `request(path, init?)`

Uma chamada autenticada: `Authorization: Bearer`, JSON na ida e na volta, erros como `VimineError`, uma nova tentativa
depois de uma renovação quando a resposta é `401`. `init.idempotencyKey` vira o cabeçalho `Idempotency-Key`.

```js
const events = await vimine.request('/v1/me/membership/events?limit=5');
```

### `me`

| | Escopo | |
|---|---|---|
| `me.get()` | `profile:read` | o perfil (`Me`), com `codicefiscale_check`: como a pessoa está identificada (`null` = ainda não) |
| `me.update(patch)` | `profile:write` | telefone, sexo, e a identificação: data, local e país de nascimento (`birth_country`), código fiscal — opcional, pode ser adicionado depois. Um código que não confere responde `422` com uma entrada `codicefiscale_mismatch`; quando ela é `confirmable`, a pessoa o confirma com `codicefiscale_confirmed: true` ([guia](https://developers.vimine.network/pt/guides/members-without-cf/)) |

### `membership`

| | Escopo | |
|---|---|---|
| `membership.get()` | `membership:read` | a carteirinha: `status`, `verdict`, `expires_at`, `statuto`, `card` |
| `membership.request(body, { idempotencyKey? })` | `membership:request` | pedido de adesão; uma chave de idempotência é gerada quando ausente. `422 identification_required` quando o perfil não está identificado: `err.errors` indica o que falta, a completar com `me.update()` |
| `membership.events({ limit?, cursor? })` | `membership:read` | histórico de status, paginado |
| `membership.statuto()` | `membership:read` | o statuto em vigor e a aceitação do associado |
| `membership.acceptStatuto({ version, sha256? })` | `statuto:accept` | registra a aceitação |
| `membership.card()` | `membership:read` | o token da carteirinha (QR) e a sua janela; `409 not_active` se a carteirinha não estiver ativa |
| `membership.renew({ version, sha256? })` | `POST /v1/me/membership/renew` | renovação pelo próprio associado: na hora quando é gratuita e sem aprovação, senão um pedido à secretaria |
| `membership.setNetworkOptIn(bool)` | `PATCH /v1/me/membership/network-opt-in` | consentimento ao Network (`network:manage`) |
| `membership.googleWalletUrl()` | `GET /v1/me/membership/wallet/google` | um link "Salvar no Google Wallet"; `409 not_active` se a carteirinha não estiver ativa |
| `membership.cardSvg()` | `GET /v1/me/membership/card.svg` | a carteirinha em SVG (com o QR); `409 not_active` se a carteirinha não estiver ativa |
| `membership.emailCard()` | `POST /v1/me/membership/card/email` | envia a carteirinha por e-mail ao associado |

### `events`

Os eventos publicados da associação, incluindo os só para associados (`events:read`). Os públicos não precisam de
login: `vimine.public.events`.

| | |
|---|---|
| `events.list({ filter?, from?, to?, limit? })` | `filter`: `upcoming` (padrão), `past`, `cancelled`, `all` |
| `events.get(slug)` | conteúdo, imagens, tipos de inscrição com as vagas restantes |
| `events.eligibility(slug)` | o associado pode se inscrever? por tipo, com os motivos (`membership_required`, `sold_out`, …) |

### `registrations`

As inscrições do associado (`registrations:read` / `registrations:write`). Veja o guia [Eventos e inscrições](https://developers.vimine.network/pt/guides/events/).

| | |
|---|---|
| `registrations.create(slug, { registration_type, note?, quantity? }, opts?)` | tipo gratuito → `confirmed` com o `token` do QR; com contribuição → `reserved` com `payment_instructions`; `422 not_eligible` com os motivos; `Idempotency-Key` gerada |
| `registrations.list({ filter? })` · `registrations.get(id)` | as inscrições do associado (`filter: 'upcoming'`) |
| `registrations.cancel(id)` | cancela dentro da janela do evento (`409 window_closed` depois) |
| `registrations.tokenPayload(id)` | o conteúdo do QR de uma inscrição confirmada, ou `null` |

### `connections`

| | |
|---|---|
| `connections.list()` | os apps que o associado autorizou nesta associação |
| `connections.revoke(clientId)` | retira o consentimento de um app (as sessões dele terminam) |

### `public`

Os endpoints públicos, sem credenciais: veja [`publicApi`](https://developers.vimine.network/pt/sdk/public/).

## Storage

| `storage` | Onde | Quando |
|---|---|---|
| `'session'` (padrão) | `sessionStorage`: por aba, some quando ela é fechada | o padrão mais seguro para um cliente público |
| `'local'` | `localStorage`: compartilhado entre as abas, sobrevive às reinicializações; o logout se propaga às outras abas | apps em que o associado espera continuar conectado |
| `'memory'` | só na memória | SSR, testes, quiosques |
| `TokenStorage` | um objeto seu `{ get, set, remove }` | Node, storages criptografados, apps nativos |

Onde o storage do navegador não está disponível (modo privado, `about:blank`), o SDK recorre à memória.
`MemoryStorage` é exportado.

## Utilitários

- `createPkce()` → `{ verifier, challenge, method: 'S256' }`, `createVerifier()`, `createChallenge(verifier)`,
  `createState()`, `base64url(bytes)` — PKCE e `state` sobre WebCrypto, se você montar o fluxo à mão.
- `DEFAULT_API_URL`, `DEFAULT_APP_URL`.
- `publicApi(options?)` — veja [API pública](https://developers.vimine.network/pt/sdk/public/).
- `VimineError` — veja [Erros](https://developers.vimine.network/pt/sdk/errors/).
