# Vimine (browser)

> Reference of the Vimine class of @vimine/sdk — sign-in, session, API calls, storage.

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

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

`Vimine` handles **Sign in with Vimine** (OAuth 2.1 + PKCE), the member's session and the calls to the
member API. One instance per client; create it once and reuse it.

## Options

| Option | Type | Default | |
|---|---|---|---|
| `clientId` | `string` | — | your `vmc_…` (required) |
| `redirectUri` | `string` | — | the page that calls `handleRedirect()`, registered for the client (required) |
| `scopes` | `Scope[]` | `['profile:read', 'membership:read']` | permissions asked by `signIn()` |
| `storage` | `'session' \| 'local' \| 'memory' \| TokenStorage` | `'session'` | where the session and the pending sign-in live |
| `storageKey` | `string` | `vimine:<clientId>:` | key prefix in the storage |
| `locale` | `Locale` | `'it'` | language of the sign-in and consent pages |
| `apiUrl` | `string` | `https://api.vimine.network` | API origin |
| `appUrl` | `string` | `https://app.vimine.network` | origin of the sign-in pages |
| `fetch` | `typeof fetch` | `globalThis.fetch` | for tests and polyfills |
| `navigate` | `(url) => void` | `top.location.assign` | how to leave the page |

The constructor throws a `VimineError` (`invalid_options`) without `clientId` or `redirectUri`.

## Sign-in

### `signIn(options?)`

Sends the **top window** to the sign-in and consent page. Call it from a user gesture (a click). From inside
a cross-origin iframe the navigation is refused (`framed`): the sign-in page is never shown in a frame.

| Option | |
|---|---|
| `scopes` | permissions of this sign-in |
| `prompt` | `'login'` (credentials always) or `'consent'` (consent always) |
| `returnTo` | your own state, kept locally and returned by `handleRedirect()` — it is not sent |

### `createSignInUrl(options?)`

Builds the authorization URL and remembers the pending sign-in (PKCE verifier and `state`) without
navigating. Useful when you render the link yourself.

### `hasRedirectParams(url?)`

`true` when the URL (default: the current page) carries the parameters of a return (`code`+`state` or `error`).

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

On the callback page: checks the `state`, exchanges the code for tokens and stores the session. Returns
`{ session, returnTo }`, or `null` when the URL carries no sign-in parameters. With `cleanUrl` (default
`true`) it removes `code`, `state` and `error` from the address bar.

Errors (`VimineError.code`): `access_denied` and the other OAuth errors when the member did not authorise;
`no_flow` when no sign-in was started from this browser (storage empty or expired); `state_mismatch` when the
return does not match; `invalid_grant` when the code expired or was already used.

## Session

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

The stored session, or `null`.

```ts
interface Session {
    accessToken: string;
    refreshToken: string | null;
    expiresAt: number;            // ms, absolute
    scope: Scope[];
    sub: string | null;           // the member's identifier for your association (vms_…)
    mode?: 'live' | 'test';       // 'test' for sessions of a test client
    aps?: AssociationRef | null;  // the association: slug, name, picture_url
}
```

### `getAccessToken()`

A valid access token, refreshed when it expires within 30 seconds. Throws `not_signed_in` without a session.

### `refresh()`

Rotates the session with the refresh token (a single request even when called concurrently). When the refresh
token is no longer usable the session is cleared and the error rethrown.

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

Revokes the refresh token (RFC 7009, best effort) and forgets the session. With `everywhere: true` it then
sends the browser to the Vimine app's `/oauth/logout`, which ends the member's session and comes back to
`postLogoutRedirectUri` (default `redirectUri`; it must be on a registered origin).

### `onChange(listener)`

`listener(session | null)` on every sign-in, refresh and sign-out; with `storage: 'local'` also for those that
happened in other tabs. Returns the unsubscribe function.

## Member API

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

An authenticated call: `Authorization: Bearer`, JSON in and out, errors as `VimineError`, one retry after a
refresh when the response is `401`. `init.idempotencyKey` becomes the `Idempotency-Key` header.

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

### `me`

| | Scope | |
|---|---|---|
| `me.get()` | `profile:read` | the profile (`Me`), with `codicefiscale_check`: how the person is identified (`null` = not yet) |
| `me.update(patch)` | `profile:write` | phone, gender, and the identification: date, place and country of birth (`birth_country`), fiscal code — optional, may be added later. A code that disagrees answers `422` with a `codicefiscale_mismatch` entry; when it is `confirmable`, the person confirms it with `codicefiscale_confirmed: true` ([guide](https://developers.vimine.network/en/guides/members-without-cf/)) |

### `membership`

| | Scope | |
|---|---|---|
| `membership.get()` | `membership:read` | the card: `status`, `verdict`, `expires_at`, `statuto`, `card` |
| `membership.request(body, { idempotencyKey? })` | `membership:request` | membership request; an idempotency key is generated when absent. `422 identification_required` when the profile is not identified: `err.errors` names what is missing, completed with `me.update()` |
| `membership.events({ limit?, cursor? })` | `membership:read` | status history, paginated |
| `membership.statuto()` | `membership:read` | the in-force statuto and the member's acceptance |
| `membership.acceptStatuto({ version, sha256? })` | `statuto:accept` | records the acceptance |
| `membership.card()` | `membership:read` | the card token (QR) and its window; `409 not_active` unless the membership is active |
| `membership.renew({ version, sha256? })` | `POST /v1/me/membership/renew` | self-service renewal: at once when free with no approval gate, otherwise a request to the secretariat |
| `membership.setNetworkOptIn(bool)` | `PATCH /v1/me/membership/network-opt-in` | network consent (`network:manage`) |
| `membership.googleWalletUrl()` | `GET /v1/me/membership/wallet/google` | a "Save to Google Wallet" link; `409 not_active` unless the membership is active |
| `membership.cardSvg()` | `GET /v1/me/membership/card.svg` | the card as SVG (with the QR); `409 not_active` unless the membership is active |
| `membership.emailCard()` | `POST /v1/me/membership/card/email` | e-mails the member their card |

### `events`

The association's published events, members-only ones included (`events:read`). The public ones need no
sign-in: `vimine.public.events`.

| | |
|---|---|
| `events.list({ filter?, from?, to?, limit? })` | `filter`: `upcoming` (default), `past`, `cancelled`, `all` |
| `events.get(slug)` | copy, images, registration types with places left |
| `events.eligibility(slug)` | may the member register? per type, with reasons (`membership_required`, `sold_out`, …) |

### `registrations`

The member's registrations (`registrations:read` / `registrations:write`). See the [Events and registrations](https://developers.vimine.network/en/guides/events/) guide.

| | |
|---|---|
| `registrations.create(slug, { registration_type, note?, quantity? }, opts?)` | free type → `confirmed` with the QR `token`; with a contribution → `reserved` with `payment_instructions`; `422 not_eligible` with the reasons; `Idempotency-Key` generated |
| `registrations.list({ filter? })` · `registrations.get(id)` | the member's registrations (`filter: 'upcoming'`) |
| `registrations.cancel(id)` | cancel inside the event's window (`409 window_closed` after) |
| `registrations.tokenPayload(id)` | the QR content of a confirmed registration, or `null` |

### `connections`

| | |
|---|---|
| `connections.list()` | the apps the member authorised in this association |
| `connections.revoke(clientId)` | withdraws consent from an app (its sessions end) |

### `public`

The public, credential-less endpoints: see [`publicApi`](https://developers.vimine.network/en/sdk/public/).

## Storage

| `storage` | Where | When |
|---|---|---|
| `'session'` (default) | `sessionStorage`: per tab, gone when it closes | the safest default for a public client |
| `'local'` | `localStorage`: shared across tabs, survives restarts; sign-out propagates to other tabs | apps where the member expects to stay signed in |
| `'memory'` | memory only | SSR, tests, kiosks |
| `TokenStorage` | an object of yours `{ get, set, remove }` | Node, encrypted storages, native apps |

Where browser storage is unavailable (private mode, `about:blank`) the SDK falls back to memory.
`MemoryStorage` is exported.

## Utilities

- `createPkce()` → `{ verifier, challenge, method: 'S256' }`, `createVerifier()`, `createChallenge(verifier)`,
  `createState()`, `base64url(bytes)` — PKCE and `state` on WebCrypto, if you build the flow by hand.
- `DEFAULT_API_URL`, `DEFAULT_APP_URL`.
- `publicApi(options?)` — see [Public API](https://developers.vimine.network/en/sdk/public/).
- `VimineError` — see [Errors](https://developers.vimine.network/en/sdk/errors/).
