mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
Docs for Bun.password and ws publish (#3227)
* Update websocket docs & jsdoc * Document Bun.password * Update hash encoding docs * Fix typos * Add info about user-specific data in ws * Update outdated websocket jsdoc * Replace usages of req.url * Remove log
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
name: bun-release-canary
|
||||
name: bun-release-types-canary
|
||||
concurrency: release-canary
|
||||
on:
|
||||
push:
|
||||
|
||||
@@ -4,6 +4,71 @@ Bun implements the `createHash` and `createHmac` functions from [`node:crypto`](
|
||||
|
||||
{% /callout %}
|
||||
|
||||
## `Bun.password`
|
||||
|
||||
{% callout %}
|
||||
**Note** — Added in Bun 0.6.8.
|
||||
{% /callout %}
|
||||
|
||||
`Bun.password` is a collection of utility functions for hashing and verifying passwords with various cryptographically secure algorithms.
|
||||
|
||||
```ts
|
||||
const password = "super-secure-pa$$word";
|
||||
|
||||
const hash = await Bun.password.hash(password);
|
||||
// => $argon2id$v=19$m=65536,t=2,p=1$tFq+9AVr1bfPxQdh6E8DQRhEXg/M/SqYCNu6gVdRRNs$GzJ8PuBi+K+BVojzPfS5mjnC8OpLGtv8KJqF99eP6a4
|
||||
|
||||
const isMatch = await Bun.password.verify(password, hash);
|
||||
// => true
|
||||
```
|
||||
|
||||
The second argument to `Bun.password.hash` accepts a params object that lets you pick and configure the hashing algorithm.
|
||||
|
||||
```ts
|
||||
const password = "super-secure-pa$$word";
|
||||
|
||||
// use argon2 (default)
|
||||
const argonHash = await Bun.password.hash(password, {
|
||||
algorithm: "argon2id", // "argon2id" | "argon2i" | "argon2d"
|
||||
memoryCost: 4, // memory usage in kibibytes
|
||||
timeCost: 3, // the number of iterations
|
||||
});
|
||||
|
||||
// use bcrypt
|
||||
const bcryptHash = await Bun.password.hash(password, {
|
||||
algorithm: "bcrypt",
|
||||
cost: 4, // number between 4-31
|
||||
});
|
||||
```
|
||||
|
||||
The algorithm used to create the hash is stored in the hash itself. When using `bcrypt`, the returned hash is encoded in [Modular Crypt Format](https://passlib.readthedocs.io/en/stable/modular_crypt_format.html) for compatibility with most existing `bcrypt` implementations; with `argon2` the result is encoded in the newer [PHC format](https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md).
|
||||
|
||||
The `verify` function automatically detects the algorithm based on the input hash and use the correct verification method. It can correctly infer the algorithm from both PHC- or MCF-encoded hashes.
|
||||
|
||||
```ts
|
||||
const password = "super-secure-pa$$word";
|
||||
|
||||
const hash = await Bun.password.hash(password, {
|
||||
/* config */
|
||||
});
|
||||
|
||||
const isMatch = await Bun.password.verify(password, hash);
|
||||
// => true
|
||||
```
|
||||
|
||||
Synchronous versions of all functions are also available. Keep in mind that these functions are computationally expensive, so using a blocking API may degrade application performance.
|
||||
|
||||
```ts
|
||||
const password = "super-secure-pa$$word";
|
||||
|
||||
const hash = Bun.password.hashSync(password, {
|
||||
/* config */
|
||||
});
|
||||
|
||||
const isMatch = Bun.password.verifySync(password, hash);
|
||||
// => true
|
||||
```
|
||||
|
||||
## `Bun.hash`
|
||||
|
||||
`Bun.hash` is a collection of utilities for _non-cryptographic_ hashing. Non-cryptographic hashing algorithms are optimized for speed of computation over collision-resistance or security.
|
||||
|
||||
@@ -20,7 +20,7 @@ To connect to an external socket server, create an instance of `WebSocket` with
|
||||
const socket = new WebSocket("ws://localhost:3000");
|
||||
```
|
||||
|
||||
Bun supports setting custom headers. This is a Bun-specific extension of the `WebSocket` standard.
|
||||
Bun supports setting custom headers. This is a Bun-specific extension of the `WebSocket` standard. _This will not work in browsers._
|
||||
|
||||
```ts
|
||||
const socket = new WebSocket("ws://localhost:3000", {
|
||||
@@ -150,11 +150,13 @@ type WebSocketData = {
|
||||
// TypeScript: specify the type of `data`
|
||||
Bun.serve<WebSocketData>({
|
||||
fetch(req, server) {
|
||||
const cookies = parseCookies(req.headers.get("Cookie"));
|
||||
server.upgrade(req, {
|
||||
// TS: this object must conform to WebSocketData
|
||||
data: {
|
||||
createdAt: Date.now(),
|
||||
channelId: new URL(req.url).searchParams.get("channelId"),
|
||||
authToken: cookies["X-Token"],
|
||||
},
|
||||
});
|
||||
|
||||
@@ -173,42 +175,59 @@ Bun.serve<WebSocketData>({
|
||||
});
|
||||
```
|
||||
|
||||
To connect to this server from the browser, create a new `WebSocket`.
|
||||
|
||||
```ts#browser.js
|
||||
const socket = new WebSocket("ws://localhost:3000/chat");
|
||||
|
||||
socket.addEventListener("message", event => {
|
||||
console.log(event.data);
|
||||
})
|
||||
```
|
||||
|
||||
The cookies that are currently set on the page will be sent with the WebSocket upgrade request and available on `req.headers` in the `fetch` handler. Parse these cookies to determine the identity of the connecting user and set the value of `data` accordingly.
|
||||
|
||||
## Pub/Sub
|
||||
|
||||
Bun's `ServerWebSocket` implementation implements a native publish-subscribe API for topic-based broadcasting. Individual sockets can `.subscribe()` to a topic (specified with a string identifier) and `.publish()` messages to all other subscribers to that topic. This topic-based broadcast API is similar to [MQTT](https://en.wikipedia.org/wiki/MQTT) and [Redis Pub/Sub](https://redis.io/topics/pubsub).
|
||||
|
||||
```ts
|
||||
const pubsubserver = Bun.serve<{username: string}>({
|
||||
const server = Bun.serve<{ username: string }>({
|
||||
fetch(req, server) {
|
||||
if (req.url === '/chat') {
|
||||
const cookies = getCookieFromRequest(req);
|
||||
const success = server.upgrade(req, {
|
||||
data: {username: cookies.username},
|
||||
});
|
||||
return success
|
||||
? undefined
|
||||
: new Response('WebSocket upgrade error', {status: 400});
|
||||
const url = new URL(req.url);
|
||||
if (url.pathname === "/chat") {
|
||||
console.log(`upgrade!`);
|
||||
const username = getUsernameFromReq(req);
|
||||
const success = server.upgrade(req, { data: { username } });
|
||||
return success ? undefined : new Response("WebSocket upgrade error", { status: 400 });
|
||||
}
|
||||
|
||||
return new Response('Hello world');
|
||||
return new Response("Hello world");
|
||||
},
|
||||
websocket: {
|
||||
open(ws) {
|
||||
ws.subscribe('the-group-chat');
|
||||
ws.publish('the-group-chat', `${ws.data.username} has entered the chat`);
|
||||
const msg = `${ws.data.username} has entered the chat`;
|
||||
ws.subscribe("the-group-chat");
|
||||
ws.publish("the-group-chat", msg);
|
||||
},
|
||||
message(ws, message) {
|
||||
// this is a group chat
|
||||
// so the server re-broadcasts incoming message to everyone
|
||||
ws.publish('the-group-chat', `${ws.data.username}: ${message}`);
|
||||
ws.publish("the-group-chat", `${ws.data.username}: ${message}`);
|
||||
},
|
||||
close(ws) {
|
||||
ws.unsubscribe('the-group-chat');
|
||||
ws.publish('the-group-chat', `${ws.data.username} has left the chat`);
|
||||
const msg = `${ws.data.username} has left the chat`;
|
||||
ws.unsubscribe("the-group-chat");
|
||||
ws.publish("the-group-chat", msg);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`Listening on ${server.hostname}:${server.port}`);
|
||||
```
|
||||
|
||||
Calling `.publish(data)` will send the message to all subscribers of a topic (excluding the socket that called `.publish()`).
|
||||
|
||||
## Compression
|
||||
|
||||
Per-message [compression](https://websockets.readthedocs.io/en/stable/topics/compression.html) can be enabled with the `perMessageDeflate` parameter.
|
||||
|
||||
@@ -3,7 +3,8 @@ import { parse } from "querystring";
|
||||
|
||||
export default {
|
||||
fetch(req) {
|
||||
if (req.url === "/favicon.ico") return new Response("nooo dont open favicon in editor", { status: 404 });
|
||||
const url = new URL(req.url);
|
||||
if (url.pathname === "/favicon.ico") return new Response("nooo dont open favicon in editor", { status: 404 });
|
||||
|
||||
var pathname = req.url.substring(1);
|
||||
const q = pathname.indexOf("?");
|
||||
|
||||
27
packages/bun-types/bun.d.ts
vendored
27
packages/bun-types/bun.d.ts
vendored
@@ -1599,9 +1599,9 @@ declare module "bun" {
|
||||
* ```ts
|
||||
* import { websocket, serve } from "bun";
|
||||
*
|
||||
* serve({
|
||||
* serve<{name: string}>({
|
||||
* port: 3000,
|
||||
* websocket: websocket<{name: string}>({
|
||||
* websocket: {
|
||||
* open: (ws) => {
|
||||
* console.log("Client connected");
|
||||
* },
|
||||
@@ -1611,10 +1611,11 @@ declare module "bun" {
|
||||
* close: (ws) => {
|
||||
* console.log("Client disconnected");
|
||||
* },
|
||||
* }),
|
||||
* },
|
||||
*
|
||||
* fetch(req, server) {
|
||||
* if (req.url === "/chat") {
|
||||
* const url = new URL(req.url);
|
||||
* if (url.pathname === "/chat") {
|
||||
* const upgraded = server.upgrade(req, {
|
||||
* data: {
|
||||
* name: new URL(req.url).searchParams.get("name"),
|
||||
@@ -1829,9 +1830,9 @@ declare module "bun" {
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
*import { serve, websocket } from "bun";
|
||||
*import { serve } from "bun";
|
||||
*serve({
|
||||
* websocket: websocket({
|
||||
* websocket: {
|
||||
* open: (ws) => {
|
||||
* console.log("Client connected");
|
||||
* },
|
||||
@@ -1841,9 +1842,10 @@ declare module "bun" {
|
||||
* close: (ws) => {
|
||||
* console.log("Client disconnected");
|
||||
* },
|
||||
* }),
|
||||
* },
|
||||
* fetch(req, server) {
|
||||
* if (req.url === "/chat") {
|
||||
* const url = new URL(req.url);
|
||||
* if (url.pathname === "/chat") {
|
||||
* const upgraded = server.upgrade(req);
|
||||
* if (!upgraded) {
|
||||
* return new Response("Upgrade failed", { status: 400 });
|
||||
@@ -2059,9 +2061,9 @@ declare module "bun" {
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* import { serve, websocket } from "bun";
|
||||
* import { serve } from "bun";
|
||||
* serve({
|
||||
* websocket: websocket({
|
||||
* websocket: {
|
||||
* open: (ws) => {
|
||||
* console.log("Client connected");
|
||||
* },
|
||||
@@ -2071,9 +2073,10 @@ declare module "bun" {
|
||||
* close: (ws) => {
|
||||
* console.log("Client disconnected");
|
||||
* },
|
||||
* }),
|
||||
* },
|
||||
* fetch(req, server) {
|
||||
* if (req.url === "/chat") {
|
||||
* const url = new URL(req.url);
|
||||
* if (url.pathname === "/chat") {
|
||||
* const upgraded = server.upgrade(req);
|
||||
* if (!upgraded) {
|
||||
* return new Response("Upgrade failed", { status: 400 });
|
||||
|
||||
@@ -39,7 +39,8 @@ type User = {
|
||||
|
||||
Bun.serve<User>({
|
||||
fetch(req, server) {
|
||||
if (req.url === "/chat") {
|
||||
const url = new URL(req.url);
|
||||
if (url.pathname === "/chat") {
|
||||
if (
|
||||
server.upgrade(req, {
|
||||
data: {
|
||||
|
||||
Reference in New Issue
Block a user