# Userspace JS SDK (`@userspace-auth/userspace-js`)

Auth elements and session tokens for any web page, with no framework required.

Sign-in itself happens on your Userspace deployment's hosted pages. This package drops
auth UI into a page and hands you a token to call your own backend with.

Using React? Reach for `@userspace-auth/react` instead. It publishes the same token
contract with hooks and guards on top.

## Install

Embed it directly, and configure it from the script tag:

```html
<script
  src="https://cdn.jsdelivr.net/npm/@userspace-auth/userspace-js/dist/userspace.umd.js"
  data-base-url="https://acme.userspace.tech"
></script>
```

Or install and configure it yourself:

```sh
npm i @userspace-auth/userspace-js
```

```js
import { configureUserspace } from '@userspace-auth/userspace-js'

await configureUserspace({
  baseUrl: 'https://acme.userspace.tech',
})
```

Configuring is what registers the custom elements, so nothing renders until it has run.
Both forms also expose the API on `window.Userspace`, for a page that cannot use modules.

The script tag reads `data-base-url` and `data-auto-login`. The boolean attribute
counts `true`, `on`, and `1` as on. Only a classic script tag is read this way: a
`type="module"` script has no `document.currentScript`, so it must call
`configureUserspace` itself.

Type declarations ship with the package (`userspace.d.ts`), so an editor sees the full
surface from a plain npm install, including the strike-through on the deprecated
`fetchToken`.

## Calling your backend

`getToken()` returns a JWT your backend can verify locally, or `null` when nobody is
signed in. Send it as a bearer token:

```js
import { getToken } from '@userspace-auth/userspace-js'

async function loadProfile() {
  const token = await getToken()
  if (!token) return // signed out

  const res = await fetch('https://api.example.com/me', {
    headers: { Authorization: `Bearer ${token}` },
  })
  return res.json()
}
```

Being signed out is a value, not an error. `getToken()` rejects only when something
actually failed, such as the network or a broken response, so a `catch` here means a real
problem rather than an anonymous visitor. Tokens refresh themselves: each call returns a
cached token until it expires and fetches a new one after that.

One gap to know about: `signOut()` evicts the cached tokens, but a sign-out that happens
anywhere else (another tab, the hosted pages) does not, so a token issued before it keeps
resolving here until it expires.

Never send this token anywhere but your own backend. It also authenticates against the
Userspace API as that user.

On the backend, verify it with the Userspace SDK for your stack, for Go that is
`github.com/userspace-auth/sdk-go`. Any JOSE library works too: the token is a standard
RS256 JWT signed by keys published at `https://<your-deployment>/.well-known/jwks.json`.

### `fetchToken()` is deprecated

`fetchToken()` still works and is **removed in 2.0.0**. It returns the raw
`{ token, ... }` response and throws on a 401, so being signed out arrives as an error
rather than as a value. That disagreed with the React SDK, which has always returned
`null`, and code written against one mishandled the other in exactly that case.

```js
// before
try {
  const { token } = await fetchToken()
} catch (err) {
  /* signed out, or a real failure, indistinguishable without inspecting err */
}

// after
const token = await getToken() // null when signed out
```

## Elements

Configuring registers fifteen custom elements, including `<x-signed-in>` and
`<x-signed-out>` to show content by auth state, `<x-signin-button>` and
`<x-signout-button>`, and `<x-profile-settings>`. Every element accepts `theme` and
`primary` attributes.

```html
<x-signed-in>
  <p>You are signed in.</p>
  <x-signout-button></x-signout-button>
</x-signed-in>

<x-signed-out>
  <x-signin-button primary="emerald"></x-signin-button>
</x-signed-out>
```

`<x-signin-button>` returns the visitor to the page they clicked it on. Give it a
`redirect-uri` to send them somewhere else instead, which is what a marketing
page handing off to your app wants:

```html
<x-signin-button redirect-uri="https://app.example.com/"></x-signin-button>
```

The target must be an absolute URL whose origin is on the tenant's allowlist. The
server validates it and falls back to the app URL, so a value it does not trust is
ignored rather than honored.

`<x-authorized>` and `<x-non-authorized>` are older spellings of `<x-signed-in>` and
`<x-signed-out>`. Both are supported, and neither is preferred.

## Triggering Userspace from your own markup

`<x-trigger>` wraps your own element, rendering nothing of its own, and fires a
Userspace action when it is clicked (a tap counts as a click). The actions are
`settings`, which opens the settings popup, `sign-in`, which leaves for the hosted
sign-in page, and `sign-out`, which signs the user out. Wrap a real interactive
element, like a button or your design system's menu item, so keyboard activation
comes for free, and place the trigger inside `<x-signed-in>`: without a session
`settings` logs a console warning and opens nothing.

`sign-in` and `sign-out` take the same `redirect-uri` attribute as
`<x-signin-button>`, naming where to land afterwards.

```html
<x-signed-out>
  <x-trigger action="sign-in" redirect-uri="https://app.example.com/">
    <button class="your-cta">Get started</button>
  </x-trigger>
</x-signed-out>

<x-signed-in>
  <x-trigger action="settings">
    <button class="your-menu-item">Account settings</button>
  </x-trigger>
  <x-trigger action="sign-out">
    <button class="your-menu-item">Sign out</button>
  </x-trigger>
</x-signed-in>
```

Every action is available imperatively. All settings triggers and callers share
one popup instance.

```js
await Userspace.openSettings()
await Userspace.signOut()
```

`signIn()` leaves for the hosted sign-in page, carrying `redirect_uri` back to
the current URL so the user returns where they were — the same navigation the
`autoLogin` option performs, on a click instead of on every signed-out visit.
Pass `redirectUri` to land somewhere else, under the same allowlist rule as
`signOut`:

```js
Userspace.signIn()
Userspace.signIn({ redirectUri: 'https://app.example.com/' })
```

`signOut()` ends every session in this browser, not only the current one: the
server terminates the whole cookie-scoped session, so other tabs sign out too.
It POSTs to the sign-out endpoint, then navigates to the hosted sign-in page —
or to `redirectUri`, an absolute URL whose origin must be on the tenant's
allowlist (the server validates it; untrusted targets fall back to sign-in):

```js
await Userspace.signOut({ redirectUri: 'https://app.example.com/logged-out' })
```

`registerElements()` registers the custom elements without configuring the SDK, for
hosts that configure another way. `configureUserspace` already calls it; most pages
never need it directly.

## License

MIT.
