# Nuxt

> Install the @nexafon/vue plugin on the client only, render the components in ClientOnly and sign tokens in a server route.

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

Nuxt uses [`@nexafon/vue`](/v1/web/vue/). Importing it on the server never touches `window`; install the plugin on
the client only and render the components inside `<ClientOnly>`.

:::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 @nexafon/node
```

## 1. Runtime config

The keys are Nuxt runtime config, set when the server starts: the publishable key is
`runtimeConfig.public.nexafonPublishableKey` (from `NUXT_PUBLIC_NEXAFON_PUBLISHABLE_KEY`), and the secret key is
`runtimeConfig.nexafonSecretKey` (from `NUXT_NEXAFON_SECRET_KEY`), which stays on the server and is never under
`public`. Declare both in `nuxt.config.ts` with empty defaults.

## 2. A client-only plugin

The `.client.ts` suffix keeps the plugin out of server rendering. It reads the publishable key from the public runtime
config:

```ts title="examples/nuxt/app/plugins/nexafon.client.ts"
import { createNexafon } from '@nexafon/vue'

export default defineNuxtPlugin((nuxtApp) => {
  const { nexafonPublishableKey } = useRuntimeConfig().public
  nuxtApp.vueApp.use(
    createNexafon({
      publishableKey: nexafonPublishableKey,
      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()
      },
    }),
  )
})
```

## 3. A server route for tokens

`POST /api/nexafon-token` signs a user token with the secret key from the server runtime config:

```ts title="examples/nuxt/server/api/nexafon-token.post.ts"
import { createUserToken } from '@nexafon/node'
import type { H3Event } from 'h3'

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: NUXT_EXAMPLE_DEMO_USER_EMAIL is a demo user for local runs only, so replace this with your
// session.
function signedInUser(event: H3Event): SignedInUser | null {
  const { exampleDemoUserEmail, exampleDemoUserRole } = useRuntimeConfig(event)
  if (!exampleDemoUserEmail) return null
  return {
    id: 'demo-user-1',
    name: 'Demo User',
    role: exampleDemoUserRole || 'agent',
    email: exampleDemoUserEmail,
    // 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
}

// Signs a token for the signed-in user with the workspace secret key.
export default defineEventHandler((event) => {
  const user = signedInUser(event)
  if (!user) throw createError({ statusCode: 401 })
  if (!mayUseNexafon(user)) throw createError({ statusCode: 403 })
  const config = useRuntimeConfig(event)
  setResponseHeaders(event, { 'content-type': 'text/plain', 'cache-control': 'no-store' })
  return 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,
    },
    { secretKey: config.nexafonSecretKey },
  )
})
```

`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 `NUXT_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.
:::

## 4. The page

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

useHead({ title: 'Nexafon with Nuxt' })
</script>

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

<style>
body { margin: 0; font-family: system-ui, sans-serif; }
.support { display: grid; gap: 16px; padding: 16px; grid-template-columns: 1fr 340px; }
</style>
```

The full example is `examples/nuxt`.
