# Web SDK

> Load the SDK, start it with Nexafon.init, place the Web Components and call methods such as call(phone).

Source: https://docs.nexafone.so/v1/web/

The web SDK puts Nexafon’s screens inside any web page as Web Components. Each one renders a frame of Nexafon’s
embed pages, so your page needs no UI code and always shows the current version. It works in every framework; the
[React](/v1/web/react/), [Vue](/v1/web/vue/) and [Angular](/v1/web/angular/) wrappers add idiomatic components on
top.

## Load it

With a script tag (the CDN serves the latest v1, with fixes and new features automatically):

```html
<script src="https://app.nexafone.so/embed/v1/nexafon.js"></script>
```

Or from npm, as a typed loader that adds the same script once:

:::note[Not published yet]
The Nexafon packages are not on npm, Packagist or PyPI yet, and `go get` finds the Go module only once the Nexafon
repository is public and tagged, so installing them by name fails for now. Until then,
[install them from a checkout of the repo](/v1/quickstart/#install-from-a-checkout). The install commands shown here
are the ones to use after release.
:::

```sh
npm install @nexafon/js
```

```ts
import { loadNexafon } from '@nexafon/js'

// Resolves to null on the server (Next.js, Nuxt, Angular SSR, SvelteKit): it never touches window there.
const Nexafon = await loadNexafon()
```

## Start it

Call `init` once per page, before or after the elements are in the DOM:

```js
Nexafon.init({
  publishableKey: 'pk_live_…',
  getUserToken: async () => {
    const r = await fetch('/nexafon-token', { method: 'POST' })
    if (!r.ok) throw new Error(`The token endpoint answered ${r.status}`)
    return r.text()
  },
  appearance: { primaryColor: '#16A34A', radius: 10, colorMode: 'auto' },
  locale: 'en',
  popOutCalls: false,
})
```

| Option | Type | |
| --- | --- | --- |
| `publishableKey` | string | `pk_live_…`/`pk_test_…` for one business, or a partner’s `ppk_live_…`/`ppk_test_…` |
| `getUserToken` | `() => Promise<string> \| string` | Returns a **fresh** user token from your server each time; a token works once ([user tokens](/v1/server/user-tokens/)). Throw when your server answers an error (such as 401 when no one is signed in), so the SDK never gets an error body as a token |
| `appearance` | object | Primary colour, radius, font and light/dark mode ([appearance](/v1/web/appearance/)) |
| `locale` | `'en' \| 'so' \| 'ar'` | The embedded screens’ language; Arabic renders right to left |
| `popOutCalls` | boolean | On multi-page sites, calls run in a small window so navigating does not end them |

`init` loads a hidden **hub** frame at once: it holds the session, the one realtime connection and the call audio for
the whole page, and every visible component shares it. So the first component appears in well under a second, and a
page with an inbox, a softphone and five call buttons still opens one connection.

## Components

| Element | Shows | Attributes |
| --- | --- | --- |
| `<nexafon-app>` | The whole dashboard with Nexafon’s side menu: inbox, calls, team, settings, billing, orders… (each section only for users whose role may open it) | `store` |
| `<nexafon-inbox>` | The shared WhatsApp inbox | `store` |
| `<nexafon-softphone>` | The softphone: incoming calls, dial pad, call controls | `store` |
| `<nexafon-call-button>` | A button that calls one customer on WhatsApp | `phone` (E.164, required), `store` |

`store` opens a partner’s store first, by its `externalRef` or workspace id ([store switcher](/v1/partners/store-switcher/)).
Changing an attribute reloads that component.

Size `<nexafon-app>` and `<nexafon-inbox>` yourself (they fill their element, at least 480 px high); the softphone and
the call button size themselves to their content. Frames get `allow="microphone; autoplay; clipboard-write"`: the
browser asks for the microphone the first time a call starts.

Without custom elements you can mount the same screens imperatively:

```js
const unmount = Nexafon.mount(document.getElementById('support'), { component: 'inbox' })
// Nexafon.mount(el, { app: true }) for the whole dashboard; unmount() removes it again.
```

## Methods

| Method | Does |
| --- | --- |
| `openChat(phone)` | Opens (or starts) the chat with that customer in the inbox or app on the page |
| `call(phone)` | Calls the customer on WhatsApp from the store the user viewed last; with `popOutCalls` the call opens in the pop-out window |
| `startInternalCall(memberIds)` | Starts a team call with those members |
| `setAppearance(appearance)` | Changes colours, radius, font or mode at once, for example when your own theme switches |
| `on(event, listener)` / `off(event, listener)` | Listen to [events](/v1/web/events/); `on` returns a function that stops listening |
| `destroy()` | Removes every frame and ends the session on this page |

Methods that act through a frame return a Promise that rejects when the action is not possible, such as a call
without a microphone.

## Calls across page navigation

On a classic multi-page site a page load would cut an active call. With `popOutCalls: true` a call opens in a small
pop-out window that survives navigation; a call already live on the page can move into it and back. If the browser
blocks the pop-up, the call stays on the page and the user is asked to allow pop-ups. The
[HTML example](/v1/web/html/) shows it across two pages.

## Security model

- The publishable key starts sessions only from its allowed origins, and Nexafon’s frames load only on the
  workspace’s **embed domains** (CSP `frame-ancestors`).
- Every message between your page and the frames is origin-checked and versioned.
- Session tokens never reach your page: no `localStorage`, `sessionStorage` or cookies on your site.

See [security](/v1/security/) for the Content-Security-Policy your own page needs.
