diff --git a/docs/CODE_OF_CONDUCT.md b/docs/CODE_OF_CONDUCT.md index 0f4f4da02..ffa3d9416 100644 --- a/docs/CODE_OF_CONDUCT.md +++ b/docs/CODE_OF_CONDUCT.md @@ -14,8 +14,8 @@ diverse, inclusive, and healthy community. ## Our Standards -Examples of behavior that contribute to a positive environment for our -community includes: +Examples of behavior that contributes to a positive environment for our +community include: - Demonstrating empathy and kindness toward other people - Being respectful of differing opinions, viewpoints, and experiences diff --git a/package.json b/package.json index d464324f8..2fd55aced 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hono", - "version": "4.11.5", + "version": "4.11.6", "description": "Web framework built on Web Standards", "main": "dist/cjs/index.js", "type": "module", diff --git a/src/adapter/bun/conninfo.ts b/src/adapter/bun/conninfo.ts index b069ec841..c3d150d28 100644 --- a/src/adapter/bun/conninfo.ts +++ b/src/adapter/bun/conninfo.ts @@ -8,7 +8,13 @@ import { getBunServer } from './server' * @returns ConnInfo */ export const getConnInfo: GetConnInfo = (c: Context) => { - const server = getBunServer(c) + const server = getBunServer<{ + requestIP?: (req: Request) => { + address: string + family: string + port: number + } | null + }>(c) if (!server) { throw new TypeError('env has to include the 2nd argument of fetch.') diff --git a/src/adapter/bun/index.ts b/src/adapter/bun/index.ts index 1e1656b4e..a18d55f35 100644 --- a/src/adapter/bun/index.ts +++ b/src/adapter/bun/index.ts @@ -8,3 +8,4 @@ export { bunFileSystemModule, toSSG } from './ssg' export { createBunWebSocket, upgradeWebSocket, websocket } from './websocket' export type { BunWebSocketData, BunWebSocketHandler } from './websocket' export { getConnInfo } from './conninfo' +export { getBunServer } from './server' diff --git a/src/adapter/bun/server.test.ts b/src/adapter/bun/server.test.ts index 13a791055..f145ee1f8 100644 --- a/src/adapter/bun/server.test.ts +++ b/src/adapter/bun/server.test.ts @@ -1,10 +1,9 @@ import { Context } from '../../context' import { getBunServer } from './server' -import type { BunServer } from './server' describe('getBunServer', () => { it('Should success to pick Server', () => { - const server = {} as BunServer + const server = {} expect(getBunServer(new Context(new Request('http://localhost/'), { env: server }))).toBe( server diff --git a/src/adapter/bun/server.ts b/src/adapter/bun/server.ts index 3e88494e7..b042f0d2a 100644 --- a/src/adapter/bun/server.ts +++ b/src/adapter/bun/server.ts @@ -4,27 +4,11 @@ */ import type { Context } from '../../context' -/** - * Bun Server Object - */ -export interface BunServer { - requestIP?: (req: Request) => { - address: string - family: string - port: number - } | null - upgrade( - req: Request, - options?: { - data: T - } - ): boolean -} - /** * Get Bun Server Object from Context + * @template T - The type of Bun Server * @param c Context * @returns Bun Server */ -export const getBunServer = (c: Context): BunServer | undefined => - ('server' in c.env ? c.env.server : c.env) as BunServer | undefined +export const getBunServer = (c: Context): T | undefined => + ('server' in c.env ? c.env.server : c.env) as T | undefined diff --git a/src/adapter/bun/websocket.ts b/src/adapter/bun/websocket.ts index 73631dda4..af5db0645 100644 --- a/src/adapter/bun/websocket.ts +++ b/src/adapter/bun/websocket.ts @@ -46,7 +46,15 @@ export const createWSContext = (ws: BunServerWebSocket): WSCon } export const upgradeWebSocket: UpgradeWebSocket = defineWebSocketHelper((c, events) => { - const server = getBunServer(c) + const server = getBunServer<{ + upgrade( + req: Request, + options?: { + data: T + } + ): boolean + }>(c) + if (!server) { throw new TypeError('env has to include the 2nd argument of fetch.') } diff --git a/src/helper/streaming/sse.test.tsx b/src/helper/streaming/sse.test.tsx index d51cc3eec..c5d927961 100644 --- a/src/helper/streaming/sse.test.tsx +++ b/src/helper/streaming/sse.test.tsx @@ -238,4 +238,81 @@ describe('SSE Streaming helper', () => { const decodedValue = decoder.decode(value) expect(decodedValue).toBe('data:
Error
\n\n') }) + + it('Check streamSSE handles \\r (CR) line ending correctly', async () => { + const res = streamSSE(c, async (stream) => { + await stream.writeSSE({ + data: 'Line1\rLine2', + event: 'test-cr', + }) + }) + + if (!res.body) { + throw new Error('Body is null') + } + const reader = res.body.getReader() + const decoder = new TextDecoder() + const { value } = await reader.read() + const decodedValue = decoder.decode(value) + + expect(decodedValue).toBe('event: test-cr\ndata: Line1\ndata: Line2\n\n') + }) + + it('Check streamSSE handles \\r\\n (CRLF) line ending correctly', async () => { + const res = streamSSE(c, async (stream) => { + await stream.writeSSE({ + data: 'Line1\r\nLine2', + event: 'test-crlf', + }) + }) + + if (!res.body) { + throw new Error('Body is null') + } + const reader = res.body.getReader() + const decoder = new TextDecoder() + const { value } = await reader.read() + const decodedValue = decoder.decode(value) + + expect(decodedValue).toBe('event: test-crlf\ndata: Line1\ndata: Line2\n\n') + }) + + it('Check streamSSE handles mixed line endings correctly', async () => { + const res = streamSSE(c, async (stream) => { + await stream.writeSSE({ + data: 'A\nB\rC\r\nD', + event: 'test-mixed', + }) + }) + + if (!res.body) { + throw new Error('Body is null') + } + const reader = res.body.getReader() + const decoder = new TextDecoder() + const { value } = await reader.read() + const decodedValue = decoder.decode(value) + + expect(decodedValue).toBe('event: test-mixed\ndata: A\ndata: B\ndata: C\ndata: D\n\n') + }) + + it('Check streamSSE handles consecutive \\r correctly', async () => { + const res = streamSSE(c, async (stream) => { + await stream.writeSSE({ + data: 'Left\r\rRight', + event: 'test-double-cr', + }) + }) + + if (!res.body) { + throw new Error('Body is null') + } + const reader = res.body.getReader() + const decoder = new TextDecoder() + const { value } = await reader.read() + const decodedValue = decoder.decode(value) + + // Two \r should produce an empty line in between + expect(decodedValue).toBe('event: test-double-cr\ndata: Left\ndata: \ndata: Right\n\n') + }) }) diff --git a/src/helper/streaming/sse.ts b/src/helper/streaming/sse.ts index 8c10eae51..ef9368261 100644 --- a/src/helper/streaming/sse.ts +++ b/src/helper/streaming/sse.ts @@ -18,7 +18,7 @@ export class SSEStreamingApi extends StreamingApi { async writeSSE(message: SSEMessage) { const data = await resolveCallback(message.data, HtmlEscapedCallbackPhase.Stringify, false, {}) const dataLines = (data as string) - .split('\n') + .split(/\r\n|\r|\n/) .map((line) => { return `data: ${line}` }) diff --git a/src/request/constants.ts b/src/request/constants.ts index 94b4bb932..c95a35c85 100644 --- a/src/request/constants.ts +++ b/src/request/constants.ts @@ -1 +1 @@ -export const GET_MATCH_RESULT: symbol = Symbol() +export const GET_MATCH_RESULT: unique symbol = Symbol()