Skip to content

Commit 0006eed

Browse files
authored
feat: add support for websocket handler (#327)
1 parent 44b06c6 commit 0006eed

File tree

11 files changed

+246
-87
lines changed

11 files changed

+246
-87
lines changed

README.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ Add Authentication to Nuxt applications with secured & sealed cookies sessions.
2323
- [Tree-shakable server utils](#server-utils)
2424
- [`<AuthState>` component](#authstate-component)
2525
- [Extendable with hooks](#extend-session)
26+
- [WebSocket support](#websockets-support)
2627

2728
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.
2829

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

597598
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.
598599

600+
## WebSockets Support
601+
602+
Nuxt Auth Utils is compatible with [Nitro WebSockets](https://nitro.build/guide/websocket).
603+
604+
Make sure to enable the `experimental.websocket` option in your `nuxt.config.ts`:
605+
606+
```ts
607+
export default defineNuxtConfig({
608+
nitro: {
609+
experimental: {
610+
websocket: true
611+
}
612+
}
613+
})
614+
```
615+
616+
You can use the `requireUserSession` function in the `upgrade` function to check if the user is authenticated before upgrading the WebSocket connection.
617+
618+
```ts
619+
// server/routes/ws.ts
620+
export default defineWebSocketHandler({
621+
async upgrade(request) {
622+
// Make sure the user is authenticated before upgrading the WebSocket connection
623+
await requireUserSession(request)
624+
},
625+
async open(peer) {
626+
const { user } = await requireUserSession(peer)
627+
const username = Object.values(user).filter(Boolean).join(' ')
628+
peer.send(`Hello, ${username}!`)
629+
},
630+
message(peer, message) {
631+
peer.send(`Echo: ${message}`)
632+
},
633+
})
634+
```
635+
636+
Then, in your application, you can use the [useWebSocket](https://vueuse.org/core/useWebSocket/) composable to connect to the WebSocket:
637+
638+
```vue
639+
<script setup>
640+
const { status, data, send, open, close } = useWebSocket('/ws', { immediate: false })
641+
642+
// Only open the websocket after the page is hydrated (client-only)
643+
onMounted(open)
644+
</script>
645+
646+
<template>
647+
<div>
648+
<p>Status: {{ status }}</p>
649+
<p>Data: {{ data }}</p>
650+
<p>
651+
<button @click="open">Open</button>
652+
<button @click="close(1000, 'Closing')">Close</button>
653+
<button @click="send('hello')">Send hello</button>
654+
</p>
655+
</div>
656+
</template>
657+
```
658+
599659
## Configuration
600660

601661
We leverage `runtimeConfig.session` to give the defaults option to [h3 `useSession`](https://h3.unjs.io/examples/handle-session).

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
"@adonisjs/hash": "^9.0.5",
3939
"@nuxt/kit": "^3.15.2",
4040
"defu": "^6.1.4",
41-
"h3": "^1.13.1",
41+
"h3": "^1.14.0",
4242
"hookable": "^5.5.3",
4343
"ofetch": "^1.4.1",
4444
"ohash": "^1.1.4",

playground/nuxt.config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
export default defineNuxtConfig({
22
// ssr: false,
33
extends: ['@nuxt/ui-pro'],
4-
modules: ['nuxt-auth-utils', '@nuxt/ui'],
4+
modules: ['nuxt-auth-utils', '@nuxt/ui', '@vueuse/nuxt'],
55
imports: {
66
autoImport: true,
77
},
@@ -20,6 +20,7 @@ export default defineNuxtConfig({
2020
nitro: {
2121
experimental: {
2222
database: true,
23+
websocket: true,
2324
},
2425
},
2526
auth: {

playground/package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,16 @@
1212
"@iconify-json/iconoir": "^1.2.7",
1313
"@iconify-json/logos": "^1.2.4",
1414
"@tsndr/cloudflare-worker-jwt": "^3.1.3",
15+
"@vueuse/core": "^12.5.0",
16+
"@vueuse/nuxt": "^12.5.0",
1517
"nuxt": "^3.15.2",
1618
"nuxt-auth-utils": "latest",
1719
"zod": "^3.24.1"
1820
},
1921
"devDependencies": {
2022
"better-sqlite3": "^11.8.1"
23+
},
24+
"resolutions": {
25+
"h3": "^1.14.0"
2126
}
2227
}

playground/pages/index.vue

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,14 @@
2626
>
2727
About page
2828
</UButton>
29+
<UButton
30+
to="/sockets"
31+
class="mt-2"
32+
variant="link"
33+
:padded="false"
34+
>
35+
Sockets
36+
</UButton>
2937
</div>
3038
</UPageBody>
3139
</UPage>

playground/pages/sockets.vue

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<script setup>
2+
// https:// vueuse.org/core/useWebSocket/
3+
const { status, data, send, open, close } = useWebSocket('/ws', {
4+
// disable during ssr
5+
immediate: false,
6+
})
7+
onMounted(open)
8+
</script>
9+
10+
<template>
11+
<div class="mt-4 flex flex-col gap-2">
12+
<p>Status: {{ status }}</p>
13+
<p>Data: {{ data }}</p>
14+
<div class="flex gap-2">
15+
<UButton @click="open">
16+
Open
17+
</UButton>
18+
<UButton @click="close(1000, 'Closing')">
19+
Close
20+
</UButton>
21+
<UButton @click="send('hello')">
22+
Send
23+
</UButton>
24+
</div>
25+
</div>
26+
</template>

playground/server/routes/ws.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
export default defineWebSocketHandler({
2+
async upgrade(request) {
3+
await requireUserSession(request)
4+
},
5+
async open(peer) {
6+
const { user } = await requireUserSession(peer)
7+
const username = Object.values(user).filter(Boolean).join(' ')
8+
peer.send(`Hello, ${username}!`)
9+
},
10+
message(peer, message) {
11+
peer.send(`Echo: ${message}`)
12+
},
13+
})

0 commit comments

Comments
 (0)