Skip to content

Instantly share code, notes, and snippets.

@Lyokolux
Last active October 26, 2022 08:23
Show Gist options
  • Save Lyokolux/67f66a8aa02aecdcf114d1cb78c85c62 to your computer and use it in GitHub Desktop.
Save Lyokolux/67f66a8aa02aecdcf114d1cb78c85c62 to your computer and use it in GitHub Desktop.
Server Side Events implementation in a Nuxt 3 backend of notifications retrieved from the prisma ORM. A second file provide an example of use as snippet.
import { NotificationEvent } from '~~/server/api/v1/user/notifications/live.get'
/** Init the listener on the dedicated API endpoint */
const evtSource = new EventSource("/api/live")
/** Callback on message recieved */
evtSource.onmessage = (e: MessageEvent<string>) => {
if (e.data) {
this.notifications = [...JSON.parse(e.data) as NotificationEvent[], ...this.notifications]
}
}
import type { CompatibilityEvent } from "h3"
import crypto from 'crypto'
import { getAuthContext, useAuthenticationGuard } from "~~/server/lib/auth"
import { db } from "~~/server/lib/prisma"
import { TimeInMS } from "~~/const/time"
/** Amount of time between two checks of new content */
const CHECK_INTERVAL = 10 * TimeInMS.SECOND
/**
* Amount of intervals before closing the connection from the server.
* For example if the client does not respond anymore.
*/
const INTERVALS_BEFORE_CLOSE = 10
/** Content of the SSE as TS-Type */
export type NotificationEvent = {
createdAt: Date
content: string
notificationTypeId: string
}[] | undefined
/**
* Expected format of an SSE Payload
*/
const sendEvent = (e: CompatibilityEvent, id: string, data: any) => {
e.res.write(`id: ${id}\n`)
e.res.write(`data: ${data}\n\n`)
}
/** A dummy wait interval between checks */
const sleep = (ms: number) => {
return new Promise((resolve) => setTimeout(resolve, ms))
}
export default defineEventHandler(async (event) => {
const { isNotAuthenticatedResponse } = useAuthenticationGuard(event)
if (isNotAuthenticatedResponse) {
return isNotAuthenticatedResponse
}
// Init SSE connection with proper Content-Type and Connection
event.res.writeHead(200, {
'Cache-Control': 'no-cache',
Connection: 'keep-alive',
'Content-Type': 'text/event-stream',
})
const userId = getAuthContext(event)?.email as string // defined here as there is an auth guard
let lastPull = new Date()
for (let cycleCount = 0; cycleCount < INTERVALS_BEFORE_CLOSE; cycleCount++) {
// 1. Pull the new notifications
let notifications = await db.notification.findMany({
select: { content: true, notificationTypeId: true, createdAt: true },
where: {
toUserId: userId,
createdAt: {
gte: lastPull,
}
},
orderBy: {
createdAt: 'desc'
},
})
lastPull = new Date()
// 2. Prepare the payload it there are new data
if (notifications.length > 0) {
const payload = JSON.stringify(notifications)
// 3. Send it
const sseId = crypto.randomUUID() // generate a random ID for the SSE
sendEvent(event, sseId, payload) // send a SSE with utility function
}
// 4. Wait stupidely and check again after a period of time
await sleep(CHECK_INTERVAL)
}
// close the connection after INTERVALS_BEFORE_CLOSE amount of intervals
event.res.end()
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment