Skip to content

Commit

Permalink
feat: add support for websocket handler (#327)
Browse files Browse the repository at this point in the history
  • Loading branch information
atinux authored Jan 22, 2025
1 parent 44b06c6 commit 0006eed
Show file tree
Hide file tree
Showing 11 changed files with 246 additions and 87 deletions.
60 changes: 60 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Add Authentication to Nuxt applications with secured & sealed cookies sessions.
- [Tree-shakable server utils](#server-utils)
- [`<AuthState>` component](#authstate-component)
- [Extendable with hooks](#extend-session)
- [WebSocket support](#websockets-support)

It has few dependencies (only from [UnJS](https://github.com/unjs)), run on multiple JS environments (Node, Deno, Workers) and is fully typed with TypeScript.

Expand Down Expand Up @@ -596,6 +597,65 @@ You can use the `placeholder` slot to show a placeholder on server-side and whil

If you are caching your routes with `routeRules`, please make sure to use [Nitro](https://github.com/unjs/nitro) >= `2.9.7` to support the client-side fetching of the user session.

## WebSockets Support

Nuxt Auth Utils is compatible with [Nitro WebSockets](https://nitro.build/guide/websocket).

Make sure to enable the `experimental.websocket` option in your `nuxt.config.ts`:

```ts
export default defineNuxtConfig({
nitro: {
experimental: {
websocket: true
}
}
})
```

You can use the `requireUserSession` function in the `upgrade` function to check if the user is authenticated before upgrading the WebSocket connection.

```ts
// server/routes/ws.ts
export default defineWebSocketHandler({
async upgrade(request) {
// Make sure the user is authenticated before upgrading the WebSocket connection
await requireUserSession(request)
},
async open(peer) {
const { user } = await requireUserSession(peer)
const username = Object.values(user).filter(Boolean).join(' ')
peer.send(`Hello, ${username}!`)
},
message(peer, message) {
peer.send(`Echo: ${message}`)
},
})
```

Then, in your application, you can use the [useWebSocket](https://vueuse.org/core/useWebSocket/) composable to connect to the WebSocket:

```vue
<script setup>
const { status, data, send, open, close } = useWebSocket('/ws', { immediate: false })
// Only open the websocket after the page is hydrated (client-only)
onMounted(open)
</script>
<template>
<div>
<p>Status: {{ status }}</p>
<p>Data: {{ data }}</p>
<p>
<button @click="open">Open</button>
<button @click="close(1000, 'Closing')">Close</button>
<button @click="send('hello')">Send hello</button>
</p>
</div>
</template>
```

## Configuration

We leverage `runtimeConfig.session` to give the defaults option to [h3 `useSession`](https://h3.unjs.io/examples/handle-session).
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"@adonisjs/hash": "^9.0.5",
"@nuxt/kit": "^3.15.2",
"defu": "^6.1.4",
"h3": "^1.13.1",
"h3": "^1.14.0",
"hookable": "^5.5.3",
"ofetch": "^1.4.1",
"ohash": "^1.1.4",
Expand Down
3 changes: 2 additions & 1 deletion playground/nuxt.config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export default defineNuxtConfig({
// ssr: false,
extends: ['@nuxt/ui-pro'],
modules: ['nuxt-auth-utils', '@nuxt/ui'],
modules: ['nuxt-auth-utils', '@nuxt/ui', '@vueuse/nuxt'],
imports: {
autoImport: true,
},
Expand All @@ -20,6 +20,7 @@ export default defineNuxtConfig({
nitro: {
experimental: {
database: true,
websocket: true,
},
},
auth: {
Expand Down
5 changes: 5 additions & 0 deletions playground/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,16 @@
"@iconify-json/iconoir": "^1.2.7",
"@iconify-json/logos": "^1.2.4",
"@tsndr/cloudflare-worker-jwt": "^3.1.3",
"@vueuse/core": "^12.5.0",
"@vueuse/nuxt": "^12.5.0",
"nuxt": "^3.15.2",
"nuxt-auth-utils": "latest",
"zod": "^3.24.1"
},
"devDependencies": {
"better-sqlite3": "^11.8.1"
},
"resolutions": {
"h3": "^1.14.0"
}
}
8 changes: 8 additions & 0 deletions playground/pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@
>
About page
</UButton>
<UButton
to="/sockets"
class="mt-2"
variant="link"
:padded="false"
>
Sockets
</UButton>
</div>
</UPageBody>
</UPage>
Expand Down
26 changes: 26 additions & 0 deletions playground/pages/sockets.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<script setup>
// https:// vueuse.org/core/useWebSocket/
const { status, data, send, open, close } = useWebSocket('/ws', {
// disable during ssr
immediate: false,
})
onMounted(open)
</script>

<template>
<div class="mt-4 flex flex-col gap-2">
<p>Status: {{ status }}</p>
<p>Data: {{ data }}</p>
<div class="flex gap-2">
<UButton @click="open">
Open
</UButton>
<UButton @click="close(1000, 'Closing')">
Close
</UButton>
<UButton @click="send('hello')">
Send
</UButton>
</div>
</div>
</template>
13 changes: 13 additions & 0 deletions playground/server/routes/ws.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export default defineWebSocketHandler({
async upgrade(request) {
await requireUserSession(request)
},
async open(peer) {
const { user } = await requireUserSession(peer)
const username = Object.values(user).filter(Boolean).join(' ')
peer.send(`Hello, ${username}!`)
},
message(peer, message) {
peer.send(`Echo: ${message}`)
},
})
Loading

0 comments on commit 0006eed

Please sign in to comment.