Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
# 1.4.20 - 3 Jan 2026
Improvement:
- add `ModelValidator.schema` for accessing raw schema
- [#1636](https://github.com/elysiajs/elysia/issues/1636) add `subscriptions` to `Elysia.ws` context
- [#1638](https://github.com/elysiajs/elysia/pull/1638) unref Sucrose gc

Bug fix:
- [#1649](https://github.com/elysiajs/elysia/pull/1649) add null check for object properties (t.Record)
- [#1646](https://github.com/elysiajs/elysia/pull/1646) use constant-time comparison for signed cookie verification
- [#1639](https://github.com/elysiajs/elysia/issues/1639) compose: ReferenceError: "_r_r is not defined" when onError returns plain object & mapResponse exists
- [#1631](https://github.com/elysiajs/elysia/issues/1631) update Exact Mirror to 0.2.6
- [#1630](https://github.com/elysiajs/elysia/issues/1630) enforce fetch to return MaybePromise<Response>

Bug fix:
- `Elysia.models` broke when referencing non typebox model
Comment on lines +7 to +15
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Consolidate duplicate "Bug fix:" header and add missing issue reference.

Lines 7-15 contain two separate "Bug fix:" headers, which breaks the standard changelog format used throughout the file. Additionally, the last item on line 15 lacks an issue/PR reference that all other entries include.

Merge the two sections into a single "Bug fix:" header and add a reference to the non-typebox model issue.

🔎 Proposed fix
 Bug fix:
 - [#1649](https://github.com/elysiajs/elysia/pull/1649) add null check for object properties (t.Record)
 - [#1646](https://github.com/elysiajs/elysia/pull/1646) use constant-time comparison for signed cookie verification
 - [#1639](https://github.com/elysiajs/elysia/issues/1639) compose: ReferenceError: "_r_r is not defined" when onError returns plain object & mapResponse exists
 - [#1631](https://github.com/elysiajs/elysia/issues/1631) update Exact Mirror to 0.2.6
 - [#1630](https://github.com/elysiajs/elysia/issues/1630) enforce fetch to return MaybePromise<Response>
-
-Bug fix:
-- `Elysia.models` broke when referencing non typebox model
+- `Elysia.models` broke when referencing non typebox model

Note: If the last item references a specific issue/PR, include its number in the format [#XXXX](link) before the description.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Bug fix:
- [#1649](https://github.com/elysiajs/elysia/pull/1649) add null check for object properties (t.Record)
- [#1646](https://github.com/elysiajs/elysia/pull/1646) use constant-time comparison for signed cookie verification
- [#1639](https://github.com/elysiajs/elysia/issues/1639) compose: ReferenceError: "_r_r is not defined" when onError returns plain object & mapResponse exists
- [#1631](https://github.com/elysiajs/elysia/issues/1631) update Exact Mirror to 0.2.6
- [#1630](https://github.com/elysiajs/elysia/issues/1630) enforce fetch to return MaybePromise<Response>
Bug fix:
- `Elysia.models` broke when referencing non typebox model
Bug fix:
- [#1649](https://github.com/elysiajs/elysia/pull/1649) add null check for object properties (t.Record)
- [#1646](https://github.com/elysiajs/elysia/pull/1646) use constant-time comparison for signed cookie verification
- [#1639](https://github.com/elysiajs/elysia/issues/1639) compose: ReferenceError: "_r_r is not defined" when onError returns plain object & mapResponse exists
- [#1631](https://github.com/elysiajs/elysia/issues/1631) update Exact Mirror to 0.2.6
- [#1630](https://github.com/elysiajs/elysia/issues/1630) enforce fetch to return MaybePromise<Response>
- `Elysia.models` broke when referencing non typebox model
🤖 Prompt for AI Agents
In CHANGELOG.md around lines 7 to 15 there are duplicate "Bug fix:" headers and
the final bullet lacks an issue/PR reference; merge the two "Bug fix:" sections
into one contiguous "Bug fix:" header containing all bullets, and update the
last bullet to include the missing issue/PR reference in the same format as the
others (e.g. add `[#XXXX](https://github.com/elysiajs/elysia/pull/XXXX)` or
`[#XXXX](https://github.com/elysiajs/elysia/issues/XXXX)` immediately before the
description of "Elysia.models broke when referencing non typebox model").


# 1.4.19 - 13 Dec 2025
Security:
- reject invalid cookie signature when using cookie rotation
Expand Down
13 changes: 5 additions & 8 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 13 additions & 10 deletions example/a.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { Elysia } from '../src'
import { Elysia, t } from '../src'
import { z } from 'zod'
import { req } from '../test/utils'
Comment on lines +1 to +3
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove unused imports.

The imports t, z, and req are not used anywhere in the code. Since this is an example file meant to demonstrate library usage, removing unused imports will improve clarity and reduce confusion.

🔎 Proposed fix
-import { Elysia, t } from '../src'
-import { z } from 'zod'
-import { req } from '../test/utils'
+import { Elysia } from '../src'
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { Elysia, t } from '../src'
import { z } from 'zod'
import { req } from '../test/utils'
import { Elysia } from '../src'
🤖 Prompt for AI Agents
In example/a.ts lines 1-3, remove the unused imports t, z, and req from the
import statements; keep only the imports actually used (e.g., Elysia) so the
file only imports required symbols and avoid unused-import lint errors/clarity
issues.


// This uses aot: true by default in 1.4 (broken on Bun)
const app = new Elysia({ systemRouter: true })
.get("/", "Hello Elysia")
.get("/json", () => ({ message: "Hello World", timestamp: Date.now() }))

Bun.serve({
port: 3000,
fetch: app.fetch
})
export const app = new Elysia()
.ws('/', {
open(ws) {
ws.subscribe('a')
},
message(a) {
console.log(a.subscriptions)
}
})
.listen(3000)
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "elysia",
"description": "Ergonomic Framework for Human",
"version": "1.4.19",
"version": "1.4.20",
"author": {
"name": "saltyAom",
"url": "https://github.com/SaltyAom",
Expand Down Expand Up @@ -191,13 +191,13 @@
},
"dependencies": {
"cookie": "^1.1.1",
"exact-mirror": "0.2.5",
"exact-mirror": "^0.2.6",
"fast-decode-uri-component": "^1.0.1",
"memoirist": "^0.4.0"
},
"devDependencies": {
"@elysiajs/openapi": "^1.4.1",
"@types/bun": "^1.2.12",
"@types/bun": "^1.3.5",
"@types/cookie": "1.0.0",
"@types/fast-decode-uri-component": "^1.0.0",
"@typescript-eslint/eslint-plugin": "^8.30.1",
Expand Down
9 changes: 6 additions & 3 deletions src/compose.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ import type {
Handler,
HookContainer,
LifeCycleStore,
MaybePromise,
SchemaValidator
} from './types'
import { tee } from './adapter/utils'
Expand Down Expand Up @@ -2308,7 +2309,9 @@ export const createHoc = (app: AnyElysia, fnName = 'map') => {
return `return function hocMap(${adapter.parameters}){return ${handler}(${adapter.parameters})}`
}

export const composeGeneralHandler = (app: AnyElysia) => {
export const composeGeneralHandler = (
app: AnyElysia
): ((request: Request) => MaybePromise<Response>) => {
const adapter = app['~adapter'].composeGeneralHandler
app.router.http.build()

Expand Down Expand Up @@ -2658,7 +2661,7 @@ export const composeErrorHandler = (app: AnyElysia) => {
`return mapResponse(_r,set${adapter.mapResponseContext})}` +
`if(_r instanceof ElysiaCustomStatusResponse){` +
`error.status=error.code\n` +
`error.message = error.response` +
`error.message=error.response` +
`}` +
`if(set.status===200||!set.status)set.status=error.status\n`

Expand All @@ -2676,7 +2679,7 @@ export const composeErrorHandler = (app: AnyElysia) => {
)

fnLiteral +=
`context.response=context.responseValue=_r` +
`context.response=context.responseValue=_r\n` +
`_r=${isAsyncName(mapResponse) ? 'await ' : ''}onMapResponse[${i}](context)\n`

endUnit()
Expand Down
5 changes: 4 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,10 @@ export default class Elysia<

for (const name of Object.keys(this.definitions.type))
models[name] = getSchemaValidator(
this.definitions.typebox.Import(name as never)
this.definitions.typebox.Import(name as never),
{
models: this.definitions.type
}
)

// @ts-expect-error
Expand Down
70 changes: 42 additions & 28 deletions src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {

import { t, type TypeCheck } from './type-system'

import { deepClone, mergeCookie, mergeDeep, randomId } from './utils'
import { mergeCookie, mergeDeep, randomId } from './utils'
import { mapValueError } from './error'

import type { CookieOptions } from './cookies'
Expand All @@ -28,11 +28,15 @@ import type {
MaybeArray,
StandaloneInputSchema,
StandardSchemaV1LikeValidate,
UnwrapSchema
UnwrapSchema
} from './types'

import type { StandardSchemaV1Like } from './types'
import { replaceSchemaTypeFromManyOptions, type ReplaceSchemaTypeOptions, stringToStructureCoercions } from './replace-schema'
import {
replaceSchemaTypeFromManyOptions,
type ReplaceSchemaTypeOptions,
stringToStructureCoercions
} from './replace-schema'

type MapValueError = ReturnType<typeof mapValueError>

Expand Down Expand Up @@ -127,12 +131,12 @@ export const hasAdditionalProperties = (
/**
* Resolve a schema that might be a model reference (string) to the actual schema
*/
export const resolveSchema = (
export const resolveSchema = (
schema: TAnySchema | string | undefined,
models?: Record<string, TAnySchema | StandardSchemaV1Like>,
modules?: TModule<any, any>
): TAnySchema | StandardSchemaV1Like | undefined => {
if (!schema) return undefined
): TAnySchema | StandardSchemaV1Like | undefined => {
if (!schema) return undefined
if (typeof schema !== 'string') return schema

// Check modules first (higher priority)
Expand All @@ -143,7 +147,7 @@ export const hasAdditionalProperties = (

// Then check models
return models?.[schema]
}
}

export const hasType = (type: string, schema: TAnySchema): boolean => {
if (!schema) return false
Expand All @@ -165,7 +169,11 @@ export const hasType = (type: string, schema: TAnySchema): boolean => {
if (schema.allOf) return schema.allOf.some((s: TSchema) => hasType(type, s))

if (schema.type === 'array' && schema.items) {
if (type === 'Files' && Kind in schema.items && schema.items[Kind] === 'File') {
if (
type === 'Files' &&
Kind in schema.items &&
schema.items[Kind] === 'File'
) {
return true
}
return hasType(type, schema.items)
Expand Down Expand Up @@ -459,18 +467,20 @@ export const getSchemaValidator = <
if (!schema) return undefined as any
}

const hasAdditionalCoerce = Array.isArray(additionalCoerce) ?
additionalCoerce.length > 0 : !!additionalCoerce
const hasAdditionalCoerce = Array.isArray(additionalCoerce)
? additionalCoerce.length > 0
: !!additionalCoerce

if (Kind in schema) {
if (schema[Kind] === 'Import') {
if (!hasRef(schema.$defs[schema.$ref])) {
schema = schema.$defs[schema.$ref]
schema = schema.$defs[schema.$ref] ?? models[schema.$ref]

if (coerce || hasAdditionalCoerce) {
schema = replaceSchema(schema as TSchema)
if ('$id' in schema && !schema.$defs) {
schema.$id = `${schema.$id}_coerced_${randomId()}`;
}

if ('$id' in schema && !schema.$defs)
schema.$id = `${schema.$id}_coerced_${randomId()}`
}
Comment on lines +477 to 484
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for other locations where schema.$ref and schema.$defs are accessed together
rg -n -C3 '\$defs\[\s*schema\.\$ref\s*\]' src/

Repository: elysiajs/elysia

Length of output: 1750


🏁 Script executed:

#!/bin/bash
# Read the context around lines 476-483 in src/schema.ts
sed -n '460,500p' src/schema.ts | cat -n

Repository: elysiajs/elysia

Length of output: 1377


🏁 Script executed:

#!/bin/bash
# Find the hasRef function definition
rg -n 'const hasRef|function hasRef|export const hasRef' src/

Repository: elysiajs/elysia

Length of output: 133


🏁 Script executed:

#!/bin/bash
# Get more context around line 475 to understand the logic flow
sed -n '470,490p' src/schema.ts | cat -n

Repository: elysiajs/elysia

Length of output: 783


🏁 Script executed:

#!/bin/bash
# Check the unwrapImportSchema function at lines 1351-1356
sed -n '1351,1360p' src/schema.ts | cat -n

Repository: elysiajs/elysia

Length of output: 292


🏁 Script executed:

#!/bin/bash
# Get the full hasRef function definition
sed -n '285,310p' src/schema.ts | cat -n

Repository: elysiajs/elysia

Length of output: 1032


🏁 Script executed:

#!/bin/bash
# Search for where Import schemas are created to understand their structure
rg -n "Kind.*===.*'Import'" src/ -A 3 | head -50

Repository: elysiajs/elysia

Length of output: 1645


🏁 Script executed:

#!/bin/bash
# Search for usages of unwrapImportSchema
rg -n 'unwrapImportSchema' src/

Repository: elysiajs/elysia

Length of output: 634


🏁 Script executed:

#!/bin/bash
# Also check the context to understand when Import schemas are created
# and if schema.$defs[schema.$ref] can ever be undefined
sed -n '158,165p' src/schema.ts | cat -n

Repository: elysiajs/elysia

Length of output: 338


🏁 Script executed:

#!/bin/bash
# Check how unwrapImportSchema is used in compose.ts to see if there's protection
sed -n '715,725p' src/compose.ts | cat -n

Repository: elysiajs/elysia

Length of output: 551


🏁 Script executed:

#!/bin/bash
# Check the context of another unwrapImportSchema usage
sed -n '1355,1370p' src/compose.ts | cat -n

Repository: elysiajs/elysia

Length of output: 583


🏁 Script executed:

#!/bin/bash
# Verify if the Import schema is guaranteed to have schema.$defs[schema.$ref]
# by checking how Import schemas are created
rg -n "t\.Import|Kind.*Import" src/schema.ts | grep -E "export|function" | head -10

Repository: elysiajs/elysia

Length of output: 41


🏁 Script executed:

#!/bin/bash
# Check if hasRef function can handle undefined input
sed -n '285,295p' src/schema.ts | cat -n

Repository: elysiajs/elysia

Length of output: 444


🏁 Script executed:

#!/bin/bash
# Check the critical line 475 - does hasRef protect against undefined?
# and verify if schema.$defs[schema.$ref] could be undefined at this point
rg -n "if.*hasRef\(schema\.\$defs" src/schema.ts -A 2 -B 2

Repository: elysiajs/elysia

Length of output: 41


🏁 Script executed:

#!/bin/bash
# Verify the actual potential issue - what happens if schema.$defs is undefined
# when we call hasRef(schema.$defs[schema.$ref])?
python3 << 'EOF'
# Simulate what happens at line 475
schema_defs = None  # or could be undefined
schema_ref = "someRef"

# This is what line 475 does:
try:
    result = schema_defs[schema_ref]
    print(f"Result: {result}")
except TypeError as e:
    print(f"ERROR at line 475: {e} - trying to access undefined schema.$defs")
EOF

Repository: elysiajs/elysia

Length of output: 145


🏁 Script executed:

#!/bin/bash
# Look at the exact context around line 475 with more lines
sed -n '473,478p' src/schema.ts | cat -n

Repository: elysiajs/elysia

Length of output: 312


🏁 Script executed:

#!/bin/bash
# Check if schema.$defs is guaranteed to exist before line 475
# by looking at how Import schemas are created
rg -n 'Kind.*Import' src/schema.ts -B 5 | head -40

Repository: elysiajs/elysia

Length of output: 1255


🏁 Script executed:

#!/bin/bash
# Check if there are any Import schemas created without $defs
rg -n "schema\.\[Kind\]\s*=\s*'Import'" src/ -A 5 | head -50

Repository: elysiajs/elysia

Length of output: 41


🏁 Script executed:

#!/bin/bash
# Look at the full context - is schema.$defs guaranteed to exist?
# Check what happens before line 475
sed -n '440,478p' src/schema.ts | cat -n

Repository: elysiajs/elysia

Length of output: 1377


🏁 Script executed:

#!/bin/bash
# Check if Import schemas are always created with $defs
# by looking at the t.Import or Module creation
rg -n "\.Import\(" src/ -B 3 -A 3 | head -60

Repository: elysiajs/elysia

Length of output: 1688


🏁 Script executed:

#!/bin/bash
# Check dynamic-handle.ts to see how it safely handles this
sed -n '30,40p' src/dynamic-handle.ts | cat -n

Repository: elysiajs/elysia

Length of output: 438


🏁 Script executed:

#!/bin/bash
# Check if there's a potential issue at line 475 with direct access
# Is schema.$defs always defined for Import schemas?
# Let's look for where schema.$defs could be undefined
rg -n "Kind.*===.*'Import'" src/schema.ts -A 5 | grep -E "(\$defs|schema\.\[)" | head -20

Repository: elysiajs/elysia

Length of output: 41


🏁 Script executed:

#!/bin/bash
# Check the actual issue - line 475 does direct access without checking $defs exists
# while line 476 has a fallback. Is this inconsistent?
sed -n '473,483p' src/schema.ts

Repository: elysiajs/elysia

Length of output: 428


🏁 Script executed:

#!/bin/bash
# Verify if unwrapImportSchema at line 1354 has the same vulnerability
sed -n '1351,1360p' src/schema.ts

Repository: elysiajs/elysia

Length of output: 250


🏁 Script executed:

#!/bin/bash
# Let me verify - is line 475 accessing schema.$defs without safety check?
# This could be a critical issue
sed -n '474,477p' src/schema.ts | cat -n

Repository: elysiajs/elysia

Length of output: 232


🏁 Script executed:

#!/bin/bash
# Check if hasRef handles undefined gracefully
sed -n '285,290p' src/schema.ts

Repository: elysiajs/elysia

Length of output: 254


🏁 Script executed:

#!/bin/bash
# Double-check: what happens if schema.$defs is undefined at line 475?
# Line 475: if (!hasRef(schema.$defs[schema.$ref]))
# This will throw TypeError if schema.$defs is undefined
rg -n "if.*!hasRef.*\\\$defs" src/schema.ts

Repository: elysiajs/elysia

Length of output: 108


Critical: Line 475 still has unsafe direct access to schema.$defs.

The added fallback ?? models[schema.$ref] at line 476 addresses missing references but does not prevent the TypeError at line 475 (if (!hasRef(schema.$defs[schema.$ref]))). If schema.$defs is undefined, accessing schema.$defs[schema.$ref] throws before the fallback can apply.

Both lines 475 and 476 need optional chaining for safety: schema.$defs?.[schema.$ref] (as seen in dynamic-handle.ts line 35).

Additionally, unwrapImportSchema (lines 1354-1355) has the identical vulnerability without any fallback: schema.$defs[schema.$ref][Kind] will throw if schema.$defs is undefined.

The condition at line 481 ('$id' in schema && !schema.$defs) is appropriate.

}
} else {
Expand All @@ -493,6 +503,7 @@ export const getSchemaValidator = <
}

let schema = mapSchema(s)
// console.log([s, schema])
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Remove debug console.log before merging.

This commented-out debug statement should be removed to keep the codebase clean.

🔎 Proposed fix
-	// console.log([s, schema])
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// console.log([s, schema])
🤖 Prompt for AI Agents
In src/schema.ts around line 505, there is a commented-out debug console.log("//
console.log([s, schema])"); remove this commented debug statement entirely to
clean up the codebase before merging.

let _validators = validators

if (
Expand Down Expand Up @@ -696,19 +707,19 @@ export const getSchemaValidator = <
)
schema.additionalProperties = additionalProperties
else
schema = replaceSchemaTypeFromManyOptions(schema, {
onlyFirst: "object",
from: t.Object({}),
to(schema) {
if (!schema.properties) return schema;
if ("additionalProperties" in schema) return schema;

return t.Object(schema.properties, {
...schema,
additionalProperties: false,
});
}
});
schema = replaceSchemaTypeFromManyOptions(schema, {
onlyFirst: 'object',
from: t.Object({}),
to(schema) {
if (!schema.properties) return schema
if ('additionalProperties' in schema) return schema

return t.Object(schema.properties, {
...schema,
additionalProperties: false
})
}
})
}

if (dynamic) {
Expand Down Expand Up @@ -1098,7 +1109,10 @@ export const mergeObjectSchemas = (
...newSchema.properties,
...schema.properties
},
required: [...(newSchema?.required ?? []), ...(schema.required ?? [])]
required: [
...(newSchema?.required ?? []),
...(schema.required ?? [])
]
} as TObject
}

Expand Down
3 changes: 2 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -545,7 +545,7 @@ export interface UnwrapRoute<
query: UnwrapSchema<Schema['query'], Definitions>
params: {} extends Schema['params']
? ResolvePath<Path>
: InputSchema<never> extends Schema
: {} extends Schema
? ResolvePath<Path>
: UnwrapSchema<Schema['params'], Definitions>
cookie: UnwrapSchema<Schema['cookie'], Definitions>
Expand Down Expand Up @@ -2355,6 +2355,7 @@ export interface ModelValidatorError extends ValueError {

// @ts-ignore trust me bro
export interface ModelValidator<T> extends TypeCheck<T> {
schema: T
parse(a: T): T
safeParse(a: T):
| { success: true; data: T; error: null }
Expand Down
21 changes: 11 additions & 10 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { Sucrose } from './sucrose'
import type { TraceHandler } from './trace'
import { timingSafeEqual } from 'crypto'

import type {
LifeCycleStore,
Expand Down Expand Up @@ -686,21 +685,23 @@ export const signCookie = async (val: string, secret: string | null) => {
)
}

const constantTimeEqual = (a: string, b: string) => {
// Compare as UTF-8 bytes; timingSafeEqual requires equal length
const ab = Buffer.from(a, 'utf8')
const bb = Buffer.from(b, 'utf8')
const constantTimeEqual =
typeof crypto?.timingSafeEqual === 'function'
? (a: string, b: string) => {
// Compare as UTF-8 bytes; timingSafeEqual requires equal length
const ab = Buffer.from(a, 'utf8')
const bb = Buffer.from(b, 'utf8')

if (ab.length !== bb.length) return false
return timingSafeEqual(ab, bb)
}
if (ab.length !== bb.length) return false
return crypto.timingSafeEqual(ab, bb)
}
: (a: string, b: string) => a === b

export const unsignCookie = async (input: string, secret: string | null) => {
if (typeof input !== 'string')
throw new TypeError('Signed cookie string must be provided.')

if (secret === null)
throw new TypeError('Secret key must be provided.')
if (secret === null) throw new TypeError('Secret key must be provided.')

const dot = input.lastIndexOf('.')
if (dot <= 0) return false
Expand Down
10 changes: 10 additions & 0 deletions src/ws/bun.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,16 @@ export interface ServerWebSocket<T = undefined> {
*/
isSubscribed(topic: string): boolean

/**
* Returns an array of all topics the client is currently subscribed to.
*
* @example
* ws.subscribe("chat");
* ws.subscribe("notifications");
* console.log(ws.subscriptions); // ["chat", "notifications"]
*/
readonly subscriptions: string[]

/**
* Batches `send()` and `publish()` operations, which makes it faster to send data.
*
Expand Down
Loading
Loading