fix(node-ws): adapter shouldn't send buffer as a event (#1094)
* fix(node-ws): adapter shouldn't send buffer as a event * chore: changesetpull/1095/head
parent
b18f24379b
commit
519404ad2c
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'@hono/node-ws': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Adapter won't send Buffer as a MessageEvent.
|
|
@ -1,5 +1,5 @@
|
||||||
import type { Context } from 'hono'
|
import type { Context } from 'hono'
|
||||||
import { getCookie } from 'hono/cookie';
|
import { getCookie } from 'hono/cookie'
|
||||||
import { createMiddleware } from 'hono/factory'
|
import { createMiddleware } from 'hono/factory'
|
||||||
import { HTTPException } from 'hono/http-exception'
|
import { HTTPException } from 'hono/http-exception'
|
||||||
|
|
||||||
|
|
|
@ -52,9 +52,7 @@ describe('WebSocket helper', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should be rejected if upgradeWebSocket is not used', async () => {
|
it('Should be rejected if upgradeWebSocket is not used', async () => {
|
||||||
app.get(
|
app.get('/', (c) => c.body(''))
|
||||||
'/', (c)=>c.body('')
|
|
||||||
)
|
|
||||||
|
|
||||||
{
|
{
|
||||||
const ws = new WebSocket('ws://localhost:3030/')
|
const ws = new WebSocket('ws://localhost:3030/')
|
||||||
|
@ -70,7 +68,8 @@ describe('WebSocket helper', () => {
|
||||||
expect(await mainPromise).toBe(true)
|
expect(await mainPromise).toBe(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
{ //also should rejected on fallback
|
{
|
||||||
|
//also should rejected on fallback
|
||||||
const ws = new WebSocket('ws://localhost:3030/notFound')
|
const ws = new WebSocket('ws://localhost:3030/notFound')
|
||||||
const mainPromise = new Promise<boolean>((resolve) => {
|
const mainPromise = new Promise<boolean>((resolve) => {
|
||||||
ws.onerror = () => {
|
ws.onerror = () => {
|
||||||
|
@ -202,11 +201,11 @@ describe('WebSocket helper', () => {
|
||||||
ws.send(binaryData)
|
ws.send(binaryData)
|
||||||
|
|
||||||
const receivedMessage = await mainPromise
|
const receivedMessage = await mainPromise
|
||||||
expect(receivedMessage).toBeInstanceOf(Buffer)
|
expect(receivedMessage).toBeInstanceOf(ArrayBuffer)
|
||||||
expect((receivedMessage as Buffer).byteLength).toBe(binaryData.length)
|
expect((receivedMessage as ArrayBuffer).byteLength).toBe(binaryData.length)
|
||||||
|
|
||||||
binaryData.forEach((val, idx) => {
|
binaryData.forEach((val, idx) => {
|
||||||
expect((receivedMessage as Buffer).at(idx)).toBe(val)
|
expect(new Uint8Array(receivedMessage as ArrayBuffer)[idx]).toBe(val)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,10 @@ export interface NodeWebSocketInit {
|
||||||
*/
|
*/
|
||||||
export const createNodeWebSocket = (init: NodeWebSocketInit): NodeWebSocket => {
|
export const createNodeWebSocket = (init: NodeWebSocketInit): NodeWebSocket => {
|
||||||
const wss = new WebSocketServer({ noServer: true })
|
const wss = new WebSocketServer({ noServer: true })
|
||||||
const waiterMap = new Map<IncomingMessage, { resolve: (ws: WebSocket) => void, response: Response }>()
|
const waiterMap = new Map<
|
||||||
|
IncomingMessage,
|
||||||
|
{ resolve: (ws: WebSocket) => void; response: Response }
|
||||||
|
>()
|
||||||
|
|
||||||
wss.on('connection', (ws, request) => {
|
wss.on('connection', (ws, request) => {
|
||||||
const waiter = waiterMap.get(request)
|
const waiter = waiterMap.get(request)
|
||||||
|
@ -64,9 +67,9 @@ export const createNodeWebSocket = (init: NodeWebSocketInit): NodeWebSocket => {
|
||||||
if (!waiter || waiter.response !== response) {
|
if (!waiter || waiter.response !== response) {
|
||||||
socket.end(
|
socket.end(
|
||||||
'HTTP/1.1 400 Bad Request\r\n' +
|
'HTTP/1.1 400 Bad Request\r\n' +
|
||||||
'Connection: close\r\n' +
|
'Connection: close\r\n' +
|
||||||
'Content-Length: 0\r\n' +
|
'Content-Length: 0\r\n' +
|
||||||
'\r\n'
|
'\r\n'
|
||||||
)
|
)
|
||||||
waiterMap.delete(request)
|
waiterMap.delete(request)
|
||||||
return
|
return
|
||||||
|
@ -113,7 +116,11 @@ export const createNodeWebSocket = (init: NodeWebSocketInit): NodeWebSocket => {
|
||||||
for (const data of datas) {
|
for (const data of datas) {
|
||||||
events.onMessage?.(
|
events.onMessage?.(
|
||||||
new MessageEvent('message', {
|
new MessageEvent('message', {
|
||||||
data: isBinary ? data : data.toString('utf-8'),
|
data: isBinary
|
||||||
|
? data instanceof ArrayBuffer
|
||||||
|
? data
|
||||||
|
: data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength)
|
||||||
|
: data.toString('utf-8'),
|
||||||
}),
|
}),
|
||||||
ctx
|
ctx
|
||||||
)
|
)
|
||||||
|
|
|
@ -77,13 +77,13 @@ export class AuthFlow {
|
||||||
|
|
||||||
const url = 'https://id.twitch.tv/oauth2/token'
|
const url = 'https://id.twitch.tv/oauth2/token'
|
||||||
|
|
||||||
const response = (await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
},
|
},
|
||||||
body: parsedOptions,
|
body: parsedOptions,
|
||||||
}).then((res) => res.json() as Promise<TwitchTokenResponse>))
|
}).then((res) => res.json() as Promise<TwitchTokenResponse>)
|
||||||
|
|
||||||
if ('error' in response) {
|
if ('error' in response) {
|
||||||
throw new HTTPException(400, { message: response.error })
|
throw new HTTPException(400, { message: response.error })
|
||||||
|
|
|
@ -14,18 +14,18 @@ export async function refreshToken(
|
||||||
client_secret,
|
client_secret,
|
||||||
})
|
})
|
||||||
|
|
||||||
const response = (await fetch('https://id.twitch.tv/oauth2/token', {
|
const response = await fetch('https://id.twitch.tv/oauth2/token', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
},
|
},
|
||||||
body: params,
|
body: params,
|
||||||
}).then((res) => res.json() as Promise<TwitchRefreshResponse>))
|
}).then((res) => res.json() as Promise<TwitchRefreshResponse>)
|
||||||
|
|
||||||
if ('error' in response) {
|
if ('error' in response) {
|
||||||
throw new HTTPException(400, { message: response.error })
|
throw new HTTPException(400, { message: response.error })
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('message' in response) {
|
if ('message' in response) {
|
||||||
throw new HTTPException(400, { message: response.message as string })
|
throw new HTTPException(400, { message: response.message as string })
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,7 @@ import { HTTPException } from 'hono/http-exception'
|
||||||
import { toQueryParams } from '../../utils/objectToQuery'
|
import { toQueryParams } from '../../utils/objectToQuery'
|
||||||
import type { TwitchRevokingResponse } from './types'
|
import type { TwitchRevokingResponse } from './types'
|
||||||
|
|
||||||
export async function revokeToken(
|
export async function revokeToken(client_id: string, token: string): Promise<boolean> {
|
||||||
client_id: string,
|
|
||||||
token: string
|
|
||||||
): Promise<boolean> {
|
|
||||||
const params = toQueryParams({
|
const params = toQueryParams({
|
||||||
client_id: client_id,
|
client_id: client_id,
|
||||||
token,
|
token,
|
||||||
|
@ -23,14 +20,16 @@ export async function revokeToken(
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
// Try to parse error response
|
// Try to parse error response
|
||||||
try {
|
try {
|
||||||
const errorResponse = await res.json() as TwitchRevokingResponse
|
const errorResponse = (await res.json()) as TwitchRevokingResponse
|
||||||
if (errorResponse && typeof errorResponse === 'object' && 'message' in errorResponse) {
|
if (errorResponse && typeof errorResponse === 'object' && 'message' in errorResponse) {
|
||||||
throw new HTTPException(400, { message: errorResponse.message })
|
throw new HTTPException(400, { message: errorResponse.message })
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// If parsing fails, throw a generic error with the status
|
// If parsing fails, throw a generic error with the status
|
||||||
throw new HTTPException(400, { message: `Token revocation failed with status: ${res.status}` })
|
throw new HTTPException(400, {
|
||||||
|
message: `Token revocation failed with status: ${res.status}`,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,10 +2,10 @@ export type Scopes =
|
||||||
// Analytics
|
// Analytics
|
||||||
| 'analytics:read:extensions'
|
| 'analytics:read:extensions'
|
||||||
| 'analytics:read:games'
|
| 'analytics:read:games'
|
||||||
|
|
||||||
// Bits
|
// Bits
|
||||||
| 'bits:read'
|
| 'bits:read'
|
||||||
|
|
||||||
// Channel
|
// Channel
|
||||||
| 'channel:bot'
|
| 'channel:bot'
|
||||||
| 'channel:manage:ads'
|
| 'channel:manage:ads'
|
||||||
|
@ -85,11 +85,11 @@ export type Scopes =
|
||||||
| 'moderator:read:vips'
|
| 'moderator:read:vips'
|
||||||
| 'moderator:read:warnings'
|
| 'moderator:read:warnings'
|
||||||
| 'moderator:manage:warnings'
|
| 'moderator:manage:warnings'
|
||||||
|
|
||||||
// IRC Chat Scopes
|
// IRC Chat Scopes
|
||||||
| 'chat:edit'
|
| 'chat:edit'
|
||||||
| 'chat:read'
|
| 'chat:read'
|
||||||
|
|
||||||
// PubSub-specific Chat Scopes
|
// PubSub-specific Chat Scopes
|
||||||
| 'whispers:read'
|
| 'whispers:read'
|
||||||
|
|
||||||
|
@ -110,7 +110,6 @@ export type TwitchRefreshError = Required<Pick<TwitchErrorResponse, 'status' | '
|
||||||
|
|
||||||
export type TwitchTokenError = Required<Pick<TwitchErrorResponse, 'status' | 'message' | 'error'>>
|
export type TwitchTokenError = Required<Pick<TwitchErrorResponse, 'status' | 'message' | 'error'>>
|
||||||
|
|
||||||
|
|
||||||
// Success responses types from Twitch API
|
// Success responses types from Twitch API
|
||||||
export interface TwitchValidateSuccess {
|
export interface TwitchValidateSuccess {
|
||||||
client_id: string
|
client_id: string
|
||||||
|
@ -150,19 +149,21 @@ export type TwitchTokenResponse = TwitchTokenSuccess | TwitchTokenError
|
||||||
export type TwitchValidateResponse = TwitchValidateSuccess | TwitchValidateError
|
export type TwitchValidateResponse = TwitchValidateSuccess | TwitchValidateError
|
||||||
|
|
||||||
export interface TwitchUserResponse {
|
export interface TwitchUserResponse {
|
||||||
data: [{
|
data: [
|
||||||
id: string
|
{
|
||||||
login: string
|
id: string
|
||||||
display_name: string
|
login: string
|
||||||
type: string
|
display_name: string
|
||||||
broadcaster_type: string
|
type: string
|
||||||
description: string
|
broadcaster_type: string
|
||||||
profile_image_url: string
|
description: string
|
||||||
offline_image_url: string
|
profile_image_url: string
|
||||||
view_count: number
|
offline_image_url: string
|
||||||
email: string
|
view_count: number
|
||||||
created_at: string
|
email: string
|
||||||
}]
|
created_at: string
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TwitchUser = TwitchUserResponse['data'][0]
|
export type TwitchUser = TwitchUserResponse['data'][0]
|
||||||
|
|
|
@ -1,14 +1,11 @@
|
||||||
import { HTTPException } from 'hono/http-exception'
|
import { HTTPException } from 'hono/http-exception'
|
||||||
import type { TwitchValidateResponse } from './types'
|
import type { TwitchValidateResponse } from './types'
|
||||||
|
|
||||||
export async function validateToken(
|
export async function validateToken(token: string): Promise<TwitchValidateResponse> {
|
||||||
token: string
|
|
||||||
): Promise<TwitchValidateResponse> {
|
|
||||||
|
|
||||||
const response = await fetch('https://id.twitch.tv/oauth2/validate', {
|
const response = await fetch('https://id.twitch.tv/oauth2/validate', {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
authorization: `Bearer ${token}`,
|
authorization: `Bearer ${token}`,
|
||||||
},
|
},
|
||||||
}).then((res) => res.json() as Promise<TwitchValidateResponse>)
|
}).then((res) => res.json() as Promise<TwitchValidateResponse>)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue