mirror of
https://github.com/supabase/supabase.git
synced 2026-05-11 19:26:38 +08:00
## I have read the [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) file. YES <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Replaced legacy "Anon"/"Service Role" key terminology with "Publishable Keys" and "Secret Keys" across Edge Functions guides * Updated authentication examples and request headers (client-side vs server-side) to reflect publishable/secret key usage * Standardized environment-variable examples to use parsed secret-key maps with a selectable default * Removed guidance for bypassing JWT verification via the deprecated CLI flag <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Kalleby Santos <kalleby_santos@hotmail.com>
270 lines
7.5 KiB
Plaintext
270 lines
7.5 KiB
Plaintext
---
|
|
id: 'function-WebSockets'
|
|
title: 'Handling WebSockets'
|
|
description: 'How to handle WebSocket connections in Edge Functions'
|
|
subtitle: 'Handle WebSocket connections in Edge Functions.'
|
|
---
|
|
|
|
Edge Functions supports hosting WebSocket servers that can facilitate bi-directional communications with browser clients.
|
|
|
|
This allows you to:
|
|
|
|
- Build real-time applications like chat or live updates
|
|
- Create WebSocket relay servers for external APIs
|
|
- Establish both incoming and outgoing WebSocket connections
|
|
|
|
---
|
|
|
|
## Creating WebSocket servers
|
|
|
|
Here are some basic examples of setting up WebSocket servers using Deno and Node.js APIs.
|
|
|
|
<Tabs
|
|
scrollable
|
|
size="small"
|
|
type="underlined"
|
|
defaultActiveId="deno"
|
|
queryGroup="runtime"
|
|
>
|
|
<TabPanel id="deno" label="Deno">
|
|
|
|
```ts
|
|
Deno.serve((req) => {
|
|
const upgrade = req.headers.get('upgrade') || ''
|
|
|
|
if (upgrade.toLowerCase() != 'websocket') {
|
|
return new Response("request isn't trying to upgrade to WebSocket.", { status: 400 })
|
|
}
|
|
|
|
const { socket, response } = Deno.upgradeWebSocket(req)
|
|
|
|
socket.onopen = () => console.log('socket opened')
|
|
socket.onmessage = (e) => {
|
|
console.log('socket message:', e.data)
|
|
socket.send(new Date().toString())
|
|
}
|
|
|
|
socket.onerror = (e) => console.log('socket errored:', e.message)
|
|
socket.onclose = () => console.log('socket closed')
|
|
|
|
return response
|
|
})
|
|
```
|
|
|
|
</TabPanel>
|
|
|
|
<TabPanel id="node" label="Node.js">
|
|
|
|
```ts
|
|
import { createServer } from 'node:http'
|
|
import { WebSocketServer } from 'npm:ws'
|
|
|
|
const server = createServer()
|
|
// Since we manually created the HTTP server,
|
|
// turn on the noServer mode.
|
|
const wss = new WebSocketServer({ noServer: true })
|
|
|
|
wss.on('connection', (ws) => {
|
|
console.log('socket opened')
|
|
ws.on('message', (data /** Buffer \*/, isBinary /** bool \*/) => {
|
|
if (isBinary) {
|
|
console.log('socket message:', data)
|
|
} else {
|
|
console.log('socket message:', data.toString())
|
|
}
|
|
|
|
ws.send(new Date().toString())
|
|
})
|
|
|
|
ws.on('error', (err) => {
|
|
console.log('socket errored:', err.message)
|
|
})
|
|
|
|
ws.on('close', () => console.log('socket closed'))
|
|
})
|
|
|
|
server.on('upgrade', (req, socket, head) => {
|
|
wss.handleUpgrade(req, socket, head, (ws) => {
|
|
wss.emit('connection', ws, req)
|
|
})
|
|
})
|
|
|
|
server.listen(8080)
|
|
```
|
|
|
|
</TabPanel>
|
|
</Tabs>
|
|
|
|
---
|
|
|
|
### Outbound WebSockets
|
|
|
|
You can also establish an outbound WebSocket connection to another server from an Edge Function.
|
|
|
|
Combining it with incoming WebSocket servers, it's possible to use Edge Functions as a WebSocket proxy, for example as a [relay server](https://github.com/supabase-community/openai-realtime-console?tab=readme-ov-file#using-supabase-edge-functions-as-a-relay-server) for the [OpenAI Realtime API](https://platform.openai.com/docs/guides/realtime/overview).
|
|
|
|
<$CodeSample
|
|
external={true}
|
|
org="supabase-community"
|
|
repo="openai-realtime-console"
|
|
commit="0f93657a71670704fbf77c48cf54d6c9eb956698"
|
|
path="/supabase/functions/relay/index.ts"
|
|
meta="supabase/functions/relay/index.ts"
|
|
lines={[[1, 3], [5, -1]]}
|
|
/>
|
|
|
|
---
|
|
|
|
## Authentication
|
|
|
|
WebSocket browser clients don't have the option to send custom headers. Because of this, Edge Functions won't be able to perform the usual authorization header check to verify the JWT.
|
|
|
|
You can skip the default authorization header checks by explicitly providing `--no-verify-jwt` when serving and deploying functions.
|
|
|
|
To authenticate the user making WebSocket requests, you can pass the JWT in URL query params or via a custom protocol.
|
|
|
|
<Tabs
|
|
scrollable
|
|
size="small"
|
|
type="underlined"
|
|
defaultActiveId="query"
|
|
queryGroup="auth"
|
|
>
|
|
<TabPanel id="query" label="Using query params">
|
|
|
|
```ts
|
|
import { createClient } from 'npm:@supabase/supabase-js@2'
|
|
|
|
const SUPABASE_SECRET_KEYS = JSON.parse(Deno.env.get('SUPABASE_SECRET_KEYS')!)
|
|
const supabase = createClient(
|
|
Deno.env.get('SUPABASE_URL'),
|
|
// If you want to use a different api key, change 'default' to your preferred key name
|
|
SUPABASE_SECRET_KEYS['default']
|
|
)
|
|
|
|
Deno.serve((req) => {
|
|
const upgrade = req.headers.get('upgrade') || ''
|
|
if (upgrade.toLowerCase() != 'WebSocket') {
|
|
return new Response("request isn't trying to upgrade to WebSocket.", { status: 400 })
|
|
}
|
|
|
|
// Please be aware query params may be logged in some logging systems.
|
|
const url = new URL(req.url)
|
|
const jwt = url.searchParams.get('jwt')
|
|
|
|
if (!jwt) {
|
|
console.error('Auth token not provided')
|
|
return new Response('Auth token not provided', { status: 403 })
|
|
}
|
|
|
|
const { error, data } = await supabase.auth.getClaims()
|
|
|
|
if (error) {
|
|
console.error(error)
|
|
return new Response('Invalid token provided', { status: 403 })
|
|
}
|
|
|
|
if (!data.user) {
|
|
console.error('user is not authenticated')
|
|
return new Response('User is not authenticated', { status: 403 })
|
|
}
|
|
|
|
const { socket, response } = Deno.upgradeWebSocket(req)
|
|
|
|
socket.onopen = () => console.log('socket opened')
|
|
socket.onmessage = (e) => {
|
|
console.log('socket message:', e.data)
|
|
socket.send(new Date().toString())
|
|
}
|
|
|
|
socket.onerror = (e) => console.log('socket errored:', e.message)
|
|
socket.onclose = () => console.log('socket closed')
|
|
|
|
return response
|
|
})
|
|
```
|
|
|
|
</TabPanel>
|
|
<TabPanel id="protocol" label="Using custom protocol">
|
|
|
|
```ts
|
|
import { createClient } from 'npm:@supabase/supabase-js@2'
|
|
|
|
const SUPABASE_SECRET_KEYS = JSON.parse(Deno.env.get('SUPABASE_SECRET_KEYS')!)
|
|
const supabase = createClient(
|
|
Deno.env.get('SUPABASE_URL'),
|
|
// If you want to use a different api key, change 'default' to your preferred key name
|
|
SUPABASE_SECRET_KEYS['default']
|
|
)
|
|
|
|
Deno.serve((req) => {
|
|
const upgrade = req.headers.get('upgrade') || ''
|
|
if (upgrade.toLowerCase() != 'WebSocket') {
|
|
return new Response("request isn't trying to upgrade to WebSocket.", { status: 400 })
|
|
}
|
|
|
|
// Sec-WebScoket-Protocol may return multiple protocol values `jwt-TOKEN, value1, value 2`
|
|
const customProtocols = (req.headers.get('Sec-WebSocket-Protocol') ?? '')
|
|
.split(',')
|
|
.map((p) => p.trim())
|
|
const jwt = customProtocols.find((p) => p.startsWith('jwt')).replace('jwt-', '')
|
|
|
|
if (!jwt) {
|
|
console.error('Auth token not provided')
|
|
return new Response('Auth token not provided', { status: 403 })
|
|
}
|
|
|
|
const { error, data } = await supabase.auth.getClaims()
|
|
if (error) {
|
|
console.error(error)
|
|
return new Response('Invalid token provided', { status: 403 })
|
|
}
|
|
|
|
if (!data.user) {
|
|
console.error('user is not authenticated')
|
|
return new Response('User is not authenticated', { status: 403 })
|
|
}
|
|
|
|
const { socket, response } = Deno.upgradeWebSocket(req)
|
|
|
|
socket.onopen = () => console.log('socket opened')
|
|
socket.onmessage = (e) => {
|
|
console.log('socket message:', e.data)
|
|
socket.send(new Date().toString())
|
|
}
|
|
|
|
socket.onerror = (e) => console.log('socket errored:', e.message)
|
|
socket.onclose = () => console.log('socket closed')
|
|
|
|
return response
|
|
})
|
|
```
|
|
|
|
</TabPanel>
|
|
</Tabs>
|
|
|
|
<Admonition type="caution">
|
|
|
|
The maximum duration is capped based on the wall-clock, CPU, and memory limits. The Function will shutdown when it reaches one of these [limits](/docs/guides/functions/limits).
|
|
|
|
</Admonition>
|
|
|
|
---
|
|
|
|
## Testing WebSockets locally
|
|
|
|
When testing Edge Functions locally with Supabase CLI, the instances are terminated automatically after a request is completed. This will prevent keeping WebSocket connections open.
|
|
|
|
To prevent that, you can update the `supabase/config.toml` with the following settings:
|
|
|
|
```toml
|
|
[edge_runtime]
|
|
policy = "per_worker"
|
|
```
|
|
|
|
<Admonition type="caution">
|
|
|
|
When running with `per_worker` policy, Function won't auto-reload on edits. You will need to manually restart it by running `supabase functions serve`.
|
|
|
|
</Admonition>
|