# Vue

> Install the @nexafon/vue plugin and use NexafonApp, Inbox, Softphone and CallButton with useNexafonEvent.

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

`@nexafon/vue` is a Vue plugin with components and composables around the [web SDK](/v1/web/). Supported: Vue 3.5.

:::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/vue
```

## 1. Install the plugin

`createNexafon(options)` takes the `Nexafon.init` options and starts Nexafon once; it stops when the app unmounts.

```ts title="examples/vue/src/main.ts"
import { createNexafon } from '@nexafon/vue'
import { createApp } from 'vue'
import App from './App.vue'

const getUserToken = async () => {
  const r = await fetch('/api/nexafon-token', { method: 'POST' })
  if (!r.ok) throw new Error(`The token endpoint answered ${r.status}`)
  return r.text()
}

createApp(App)
  .use(
    createNexafon({ publishableKey: import.meta.env.VITE_NEXAFON_PUBLISHABLE_KEY, getUserToken }),
  )
  .mount('#app')
```

## 2. Use the components

```vue title="examples/vue/src/App.vue"
<script setup lang="ts">
import { CallButton, Inbox, Softphone } from '@nexafon/vue'
</script>

<template>
  <main class="support">
    <Inbox style="height: 640px" />
    <aside>
      <CallButton phone="+252615550142" />
      <Softphone />
    </aside>
  </main>
</template>

<style>
.support { display: grid; gap: 16px; padding: 16px; grid-template-columns: 1fr 340px; }
</style>
```

| Component | Renders | Props |
| --- | --- | --- |
| `<NexafonApp>` | `<nexafon-app>` | `store?` |
| `<Inbox>` | `<nexafon-inbox>` | `store?` |
| `<Softphone>` | `<nexafon-softphone>` | `store?` |
| `<CallButton>` | `<nexafon-call-button>` | `phone`, `store?` |

`class` and `style` fall through to the element. `useNexafonEvent(name, listener)` listens for the component’s
lifetime; `useNexafon()` is a ref to the started API (`null` until it has started).

Writing the raw `<nexafon-*>` tags in your own templates needs
`compilerOptions.isCustomElement: (tag) => tag.startsWith('nexafon-')`; the components above do not.

## The token endpoint

`getUserToken` calls your server, which signs a token with a [server helper](/v1/server/). The example’s
`POST /api/nexafon-token` handler:

```ts title="examples/vue/server/token.ts"
import type { IncomingMessage, ServerResponse } from 'node:http'
import { createUserToken } from '@nexafon/node'

interface SignedInUser {
  id: string
  name: string
  role: string
  email: string
  emailVerified: boolean
  isStaff: boolean
}

// Your app's own session decides who is signed in, and its own records whether it has confirmed their email. The
// example has no sign-in: EXAMPLE_DEMO_USER_EMAIL is a demo user for local runs only, so replace this with your session.
function signedInUser(_req: IncomingMessage): SignedInUser | null {
  const email = process.env.EXAMPLE_DEMO_USER_EMAIL
  if (!email) return null
  return {
    id: 'demo-user-1',
    name: 'Demo User',
    role: process.env.EXAMPLE_DEMO_USER_ROLE || 'agent',
    email,
    // The demo address is a member you invited, so you have confirmed it.
    emailVerified: true,
    isStaff: true,
  }
}

// Everyone who gets a token may reach your workspace's inbox and calls: sign only for your staff, never for customers
// who signed up. The example's only user is the demo teammate.
function mayUseNexafon(user: SignedInUser): boolean {
  return user.isStaff
}

// POST /api/nexafon-token signs a token for the signed-in user with NEXAFON_SECRET_KEY.
export function userToken(req: IncomingMessage, res: ServerResponse): void {
  if (req.method !== 'POST') {
    res.writeHead(405).end()
    return
  }
  const user = signedInUser(req)
  if (!user) {
    res.writeHead(401).end()
    return
  }
  if (!mayUseNexafon(user)) {
    res.writeHead(403).end()
    return
  }
  const token = createUserToken({
    userId: user.id,
    name: user.name,
    role: user.role,
    email: user.email,
    // Nexafon links a first sign-in to the member with this email only when it is verified: never send true for an
    // address your app has not confirmed, or whoever sets a member's email on your app acts as that member.
    emailVerified: user.emailVerified,
  })
  res.writeHead(200, { 'content-type': 'text/plain', 'cache-control': 'no-store' }).end(token)
}
```

`signedInUser` stands for your app’s own session: answer `401` when nobody is signed in. The example has no sign-in,
so on a local run it signs for the demo member named in `EXAMPLE_DEMO_USER_EMAIL` and answers `401` without one;
replace it with your session. `mayUseNexafon` answers `403` to signed-in users who must not use Nexafon: sign only for
your staff, never for customers who signed up, since a token can make its user a member of your workspace. The example
treats its demo user as staff.

:::caution[Send `emailVerified` as true only for a confirmed address]
Nexafon links a user’s first sign-in to the workspace member with the token’s email only when it is verified. If your
app lets users set an email it has not confirmed, someone could register with a member’s address and act as that
member. Send `true` only when your app has confirmed the address, from your own user record, never as a constant.
:::

The example mounts it as middleware of the Vite dev and preview servers in `examples/vue/vite.config.ts`; in
production it is a route of your own server, behind your own session check.

The full example is `examples/vue`, tested with Playwright.
