Skip to content

Commit 601034b

Browse files
committed
feat: prepare to CF Workers
1 parent c2e29b3 commit 601034b

File tree

13 files changed

+1696
-755
lines changed

13 files changed

+1696
-755
lines changed

package.json

+12-9
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,15 @@
3838
"test:watch": "vitest watch"
3939
},
4040
"dependencies": {
41-
"@cloudflare/workers-types": "^4.20250121.0",
41+
"@cloudflare/workers-types": "^4.20250124.3",
4242
"@nuxt/devtools-kit": "^1.7.0",
43-
"@nuxt/kit": "^3.15.2",
43+
"@nuxt/kit": "^3.15.3",
4444
"@uploadthing/mime-types": "^0.3.4",
4545
"citty": "^0.1.6",
4646
"confbox": "^0.1.8",
4747
"defu": "^6.1.4",
4848
"destr": "^2.0.3",
49-
"h3": "^1.13.1",
49+
"h3": "^1.14.0",
5050
"mime": "^4.0.6",
5151
"nitro-cloudflare-dev": "^0.2.1",
5252
"ofetch": "^1.4.1",
@@ -62,16 +62,19 @@
6262
"@nuxt/devtools": "^1.7.0",
6363
"@nuxt/eslint-config": "^0.7.5",
6464
"@nuxt/module-builder": "^0.8.4",
65-
"@nuxt/schema": "^3.15.2",
65+
"@nuxt/schema": "^3.15.3",
6666
"@nuxt/test-utils": "^3.15.4",
6767
"@nuxthub/core": "link:",
68-
"@types/node": "^22.10.7",
68+
"@types/node": "^22.10.10",
6969
"changelogen": "^0.5.7",
70-
"eslint": "^9.18.0",
71-
"nuxt": "^3.15.2",
70+
"eslint": "^9.19.0",
71+
"nuxt": "^3.15.3",
7272
"typescript": "5.6.3",
73-
"vitest": "^3.0.3",
74-
"wrangler": "^3.104.0"
73+
"vitest": "^3.0.4",
74+
"wrangler": "^3.105.1"
75+
},
76+
"resolutions": {
77+
"h3": "^1.14.0"
7578
},
7679
"packageManager": "[email protected]"
7780
}

playground/app/layouts/default.vue

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ const links = [
2020
{ label: 'AI', to: '/ai' },
2121
{ label: 'Browser', to: '/browser' },
2222
{ label: 'Blob', to: '/blob' },
23+
{ label: 'Chat', to: '/chat' },
2324
{ label: 'Database', to: '/database' },
2425
{ label: 'KV', to: '/kv' }
2526
]

playground/app/pages/chat.vue

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<script setup lang="ts">
2+
const { user, fetch: refreshSession } = useUserSession()
3+
const { data, send, open, status } = useWebSocket(`/api/chatroom`, { immediate: false })
4+
5+
const chatContainer = ref<HTMLDivElement | null>(null)
6+
const messages = ref<{ id: string, username: string, content: string }[]>([])
7+
watch(data, async (event) => {
8+
const data = await typeof event === 'string' ? event : await event.text()
9+
messages.value.push(data[0] === '{' ? JSON.parse(data) : data)
10+
await nextTick()
11+
if (chatContainer.value) {
12+
chatContainer.value.scrollTop = chatContainer.value.scrollHeight
13+
}
14+
})
15+
16+
async function enterChat() {
17+
await $fetch('/api/login', {
18+
method: 'POST',
19+
body: { username: username.value }
20+
})
21+
await refreshSession()
22+
open()
23+
}
24+
25+
const username = ref(user.value?.username || '')
26+
const message = ref('')
27+
28+
onMounted(() => {
29+
if (user.value?.username) {
30+
console.log('open')
31+
open()
32+
}
33+
})
34+
35+
function sendMessage() {
36+
send(JSON.stringify({
37+
username: username.value,
38+
content: message.value
39+
}))
40+
message.value = ''
41+
}
42+
</script>
43+
44+
<template>
45+
<UCard v-if="user?.username && status === 'OPEN'">
46+
<div ref="chatContainer" class="h-full max-h-[400px] overflow-auto">
47+
<div v-for="(msg, index) in messages" :key="msg.id" class="flex flex-col" :class="{ 'pt-2': index > 0 && msg.username !== messages[index - 1]?.username }">
48+
<div :class="msg.username === user.username ? 'pl-8 ml-auto' : 'pr-8 mr-auto'">
49+
<!-- only show username if previous one is not from the same user -->
50+
<div v-if="index < 1 || msg.username !== messages[index - 1]?.username" class="text-xs text-gray-500">
51+
{{ msg.username }}
52+
</div>
53+
<p
54+
class="p-2 mt-1 text-sm rounded-lg text-smp-2 whitespace-pre-line"
55+
:class="msg.username === user.username ? 'text-white bg-blue-400' : 'text-gray-700 bg-gray-200'"
56+
>
57+
{{ msg.content }}
58+
</p>
59+
</div>
60+
</div>
61+
</div>
62+
<form class="flex items-center w-full pt-4 gap-2" @submit.prevent="sendMessage">
63+
<UInput v-model="message" placeholder="Send a message..." class="w-full" />
64+
<UButton icon="i-heroicons-paper-airplane" type="submit" color="black" />
65+
</form>
66+
</UCard>
67+
<UCard v-else>
68+
<form class="flex items-center gap-2" @submit.prevent="enterChat">
69+
<UInput v-model="username" placeholder="Enter your username" name="username" />
70+
<UButton color="black" :disabled="!username.trim()" :loading="status === 'CONNECTING'" type="submit">
71+
Enter Chat
72+
</UButton>
73+
</form>
74+
</UCard>
75+
</template>

playground/nuxt.config.ts

+7-11
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ export default defineNuxtConfig({
88
modules: [
99
'@nuxt/ui',
1010
'@nuxtjs/mdc',
11-
'@kgierke/nuxt-basic-auth',
11+
// '@kgierke/nuxt-basic-auth',
12+
'@vueuse/nuxt',
13+
'nuxt-auth-utils',
1214
module
1315
],
1416
devtools: { enabled: true },
@@ -18,19 +20,13 @@ export default defineNuxtConfig({
1820
},
1921
future: { compatibilityVersion: 4 },
2022

21-
// nitro: {
22-
// cloudflare: {
23-
// wrangler: {
24-
// compatibility_flags: ['nodejs_compat_v2']
25-
// }
26-
// }
27-
// },
28-
29-
compatibilityDate: '2024-08-08',
23+
compatibilityDate: '2025-01-22',
3024

3125
nitro: {
26+
preset: 'cloudflare-durable',
3227
experimental: {
33-
openAPI: true
28+
openAPI: true,
29+
websocket: true
3430
}
3531
},
3632

playground/package.json

+11-8
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,22 @@
88
"preview": "nuxi preview"
99
},
1010
"dependencies": {
11-
"@ai-sdk/vue": "^1.0.8",
11+
"@ai-sdk/vue": "^1.1.5",
1212
"@cloudflare/puppeteer": "^0.0.14",
13-
"@iconify-json/simple-icons": "^1.2.19",
13+
"@iconify-json/simple-icons": "^1.2.22",
1414
"@kgierke/nuxt-basic-auth": "^1.7.0",
15-
"@nuxt/ui": "^2.20.0",
15+
"@nuxt/ui": "^2.21.0",
1616
"@nuxthub/core": "latest",
17-
"@nuxtjs/mdc": "^0.12.1",
18-
"ai": "^4.0.30",
17+
"@nuxtjs/mdc": "^0.13.2",
18+
"@vueuse/core": "^12.5.0",
19+
"@vueuse/nuxt": "^12.5.0",
20+
"ai": "^4.1.8",
1921
"aws4fetch": "^1.0.20",
20-
"drizzle-orm": "^0.38.3",
21-
"nuxt": "^3.15.1",
22+
"drizzle-orm": "^0.39.0",
23+
"nuxt": "^3.15.3",
24+
"nuxt-auth-utils": "^0.5.10",
2225
"postgres": "^3.4.5",
23-
"puppeteer": "^23.11.1",
26+
"puppeteer": "^24.1.1",
2427
"workers-ai-provider": "^0.0.10",
2528
"zod": "^3.24.1"
2629
},

playground/server/api/chatroom.ts

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { randomUUID } from 'node:crypto'
2+
3+
export default defineWebSocketHandler({
4+
async upgrade(request) {
5+
await requireUserSession(request)
6+
},
7+
async open(peer) {
8+
const { user } = await requireUserSession(peer)
9+
10+
peer.send({ id: randomUUID(), username: 'server', content: `Welcome ${user.username}!` })
11+
peer.publish('chat', { id: randomUUID(), username: 'server', content: `${user.username} joined the chat!` })
12+
peer.subscribe('chat')
13+
},
14+
message(peer, message) {
15+
// heartbeat
16+
if (message.text().includes('ping')) {
17+
peer.send('pong')
18+
return
19+
}
20+
const msg = message.json()
21+
msg.id = randomUUID()
22+
// Chat message
23+
peer.send(msg) // echo
24+
peer.publish('chat', msg)
25+
},
26+
async close(peer) {
27+
const { user } = await getUserSession(peer)
28+
peer.publish('chat', { id: randomUUID(), username: 'server', content: `${user.username || peer} left!` })
29+
},
30+
error(peer, error) {
31+
console.error('error on WS', peer, error)
32+
}
33+
})

playground/server/api/login.post.ts

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { z } from 'zod'
2+
3+
// Used for the /api/chatroom endpoint
4+
export default defineEventHandler(async (event) => {
5+
const { username } = await readValidatedBody(event, z.object({ username: z.string().min(1) }).parse)
6+
7+
await setUserSession(event, {
8+
user: {
9+
username
10+
}
11+
})
12+
13+
return {}
14+
})

playground/shared/types/auth.d.ts

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
declare module '#auth-utils' {
2+
interface User {
3+
username: string
4+
}
5+
6+
// interface UserSession {
7+
// }
8+
9+
// interface SecureSessionData {
10+
// }
11+
}
12+
13+
export {}

0 commit comments

Comments
 (0)