mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
237 lines
7.5 KiB
Plaintext
237 lines
7.5 KiB
Plaintext
---
|
|
id: 'function-websockets'
|
|
title: 'Handling WebSockets'
|
|
description: 'How to handle WebSocket connections in Edge Functions'
|
|
subtitle: 'How to handle WebSocket connections in Edge Functions'
|
|
---
|
|
|
|
Edge Functions supports hosting WebSocket servers that can facilitate bi-directional communications with browser clients.
|
|
|
|
You can also establish outgoing WebSocket client connections to another server from Edge Functions (e.g., [OpenAI Realtime API](https://platform.openai.com/docs/guides/realtime/overview)). You can find an example OpenAI Realtime Relay Server implementation on the [supabase-community GitHub account](https://github.com/supabase-community/openai-realtime-console?tab=readme-ov-file#using-supabase-edge-functions-as-a-relay-server).
|
|
|
|
### Writing a WebSocket server
|
|
|
|
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 "jsr:@supabase/supabase-js@2";
|
|
|
|
const supabase = createClient(
|
|
Deno.env.get("SUPABASE_URL"),
|
|
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY"),
|
|
);
|
|
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.getUser(jwt);
|
|
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 "jsr:@supabase/supabase-js@2";
|
|
|
|
const supabase = createClient(
|
|
Deno.env.get("SUPABASE_URL"),
|
|
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY"),
|
|
);
|
|
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.getUser(jwt);
|
|
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>
|
|
|
|
### Limits
|
|
|
|
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).
|
|
|
|
### 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"
|
|
```
|
|
|
|
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`.
|