From a31c03dc121579365b1b6550884bba1da64b6018 Mon Sep 17 00:00:00 2001 From: watany <76135106+watany-dev@users.noreply.github.com> Date: Wed, 11 Dec 2024 08:30:18 +0000 Subject: [PATCH 1/4] v0.1 --- src/adapter/aws-lambda/client.test.ts | 152 +++++++++++++++++ src/adapter/aws-lambda/client.ts | 224 ++++++++++++++++++++++++++ 2 files changed, 376 insertions(+) create mode 100644 src/adapter/aws-lambda/client.test.ts create mode 100644 src/adapter/aws-lambda/client.ts diff --git a/src/adapter/aws-lambda/client.test.ts b/src/adapter/aws-lambda/client.test.ts new file mode 100644 index 000000000..515377676 --- /dev/null +++ b/src/adapter/aws-lambda/client.test.ts @@ -0,0 +1,152 @@ +import { HttpResponse, http } from 'msw' +import { setupServer } from 'msw/node' +import { Hono } from '../../hono' +import { hlc } from './client' // hlc をインポート +import { expect, beforeAll, afterAll, afterEach, describe, it } from 'vitest' + +const app = new Hono() + +app.post('/hash-check', async (c) => { + const payload = await c.req.json() + const sha256 = c.req.header('x-amz-content-sha256') + return c.json({ receivedHash: sha256, payload }, 200) +}) + +app.put('/hash-check', async (c) => { + const payload = await c.req.json() + const sha256 = c.req.header('x-amz-content-sha256') + return c.json({ receivedHash: sha256, payload }, 200) +}) + +// テスト用のモックサーバーを定義 +const server = setupServer( + http.post('http://localhost/hash-check', async ({ request }) => { + const sha256 = request.headers.get('x-amz-content-sha256') + const payload = await request.json() + + const calculateSHA256 = async (message: string): Promise => { + const msgBuffer = new TextEncoder().encode(message) + const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer) + const hashBytes = new Uint8Array(hashBuffer) + let hashHex = '' + for (let i = 0; i < hashBytes.length; i++) { + const b = hashBytes[i] + hashHex += b < 16 ? '0' + b.toString(16) : b.toString(16) + } + return hashHex + } + + const expectedHash = await calculateSHA256(JSON.stringify(payload)) + + if (sha256 === expectedHash) { + return HttpResponse.json({ result: 'ok', receivedHash: sha256, payload }) + } else { + return HttpResponse.json({ result: 'mismatch', receivedHash: sha256, expectedHash }, { status: 400 }) + } + }), + + http.put('http://localhost/hash-check', async ({ request }) => { + const sha256 = request.headers.get('x-amz-content-sha256') + const payload = await request.json() + + const calculateSHA256 = async (message: string): Promise => { + const msgBuffer = new TextEncoder().encode(message) + const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer) + const hashBytes = new Uint8Array(hashBuffer) + let hashHex = '' + for (let i = 0; i < hashBytes.length; i++) { + const b = hashBytes[i] + hashHex += b < 16 ? '0' + b.toString(16) : b.toString(16) + } + return hashHex + } + + const expectedHash = await calculateSHA256(JSON.stringify(payload)) + if (sha256 === expectedHash) { + return HttpResponse.json({ result: 'ok', receivedHash: sha256, payload }) + } else { + return HttpResponse.json({ result: 'mismatch', receivedHash: sha256, expectedHash }, { status: 400 }) + } + }) +) + +beforeAll(() => server.listen()) +afterEach(() => server.resetHandlers()) +afterAll(() => server.close()) + +describe('x-amz-content-sha256 header tests', () => { + // type AppType を正しく定義 + type AppType = typeof app + + // hlc を使用してクライアントを作成 + const client = hlc('http://localhost') + + it('Should send correct x-amz-content-sha256 header on POST', async () => { + const payload = { name: 'Alice', message: 'Hello World' } + const res = await client['hash-check'].$post({ json: payload }) + expect(res.ok).toBe(true) + const data = await res.json() + expect(data.result).toBe('ok') + expect(data.payload).toEqual(payload) + expect(data.receivedHash).toBeDefined() + // ここで x-amz-content-sha256 が正しく計算されていることを確認 + const calculateSHA256 = async (message: string): Promise => { + const msgBuffer = new TextEncoder().encode(message) + const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer) + const hashBytes = new Uint8Array(hashBuffer) + let hashHex = '' + for (let i = 0; i < hashBytes.length; i++) { + const b = hashBytes[i] + hashHex += b < 16 ? '0' + b.toString(16) : b.toString(16) + } + return hashHex + } + const expectedHash = await calculateSHA256(JSON.stringify(payload)) + expect(data.receivedHash).toBe(expectedHash) + }) + + it('Should send correct x-amz-content-sha256 header on PUT', async () => { + const payload = { user: 'Bob', comment: 'This is a test' } + const res = await client['hash-check'].$put({ json: payload }) + expect(res.ok).toBe(true) + const data = await res.json() + expect(data.result).toBe('ok') + expect(data.payload).toEqual(payload) + expect(data.receivedHash).toBeDefined() + const calculateSHA256 = async (message: string): Promise => { + const msgBuffer = new TextEncoder().encode(message) + const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer) + const hashBytes = new Uint8Array(hashBuffer) + let hashHex = '' + for (let i = 0; i < hashBytes.length; i++) { + const b = hashBytes[i] + hashHex += b < 16 ? '0' + b.toString(16) : b.toString(16) + } + return hashHex + } + const expectedHash = await calculateSHA256(JSON.stringify(payload)) + expect(data.receivedHash).toBe(expectedHash) + }) + + it('Should fail if no JSON is provided on POST (no hash)', async () => { + // JSON未指定の場合はヘッダ付与されないはず + // この場合は hash-check サーバー側でペイロードなし ⇒ 一致しないので 400 が返ることを確認 + const res = await client['hash-check'].$post() + expect(res.ok).toBe(false) + expect(res.status).toBe(400) + const data = await res.json() + expect(data.result).toBe('mismatch') + expect(data.receivedHash).toBeUndefined() + expect(data.expectedHash).toBeDefined() + }) + + it('Should fail if no JSON is provided on PUT (no hash)', async () => { + const res = await client['hash-check'].$put() + expect(res.ok).toBe(false) + expect(res.status).toBe(400) + const data = await res.json() + expect(data.result).toBe('mismatch') + expect(data.receivedHash).toBeUndefined() + expect(data.expectedHash).toBeDefined() + }) +}) diff --git a/src/adapter/aws-lambda/client.ts b/src/adapter/aws-lambda/client.ts new file mode 100644 index 000000000..02ccc92a3 --- /dev/null +++ b/src/adapter/aws-lambda/client.ts @@ -0,0 +1,224 @@ +import crypto from 'node:crypto' +import type { Hono } from '../../hono' +import type { FormValue, ValidationTargets } from '../../types' +import { serialize } from '../../utils/cookie' +import type { UnionToIntersection } from '../../utils/types' +import type { Callback, Client, ClientRequestOptions } from '../../client/types' +import { + buildSearchParams, + deepMerge, + mergePath, + removeIndexString, + replaceUrlParam, + replaceUrlProtocol, +} from '../../client/utils' + +// --- 追加:SHA-256計算関数 --- +const calculateSHA256 = async (message: string): Promise => { + const msgBuffer = new TextEncoder().encode(message); + const hashBuffer = await crypto.subtle.digest("SHA-256", msgBuffer); + const hashBytes = new Uint8Array(hashBuffer); + + let hashHex = ""; + for (let i = 0; i < hashBytes.length; i++) { + const b = hashBytes[i]; + hashHex += b < 16 ? '0' + b.toString(16) : b.toString(16); + } + + return hashHex; +}; + +const createProxy = (callback: Callback, path: string[]) => { + const proxy: unknown = new Proxy(() => {}, { + get(_obj, key) { + if (typeof key !== 'string' || key === 'then') { + return undefined + } + return createProxy(callback, [...path, key]) + }, + apply(_1, _2, args) { + return callback({ + path, + args, + }) + }, + }) + return proxy +} + +class ClientRequestImpl { + private url: string + private method: string + private queryParams: URLSearchParams | undefined = undefined + private pathParams: Record = {} + private rBody: BodyInit | undefined + private cType: string | undefined = undefined + + constructor(url: string, method: string) { + this.url = url + this.method = method + } + fetch = async ( + args?: ValidationTargets & { + param?: Record + }, + opt?: ClientRequestOptions + ) => { + if (args) { + if (args.query) { + this.queryParams = buildSearchParams(args.query) + } + + if (args.form) { + const form = new FormData() + for (const [k, v] of Object.entries(args.form)) { + if (Array.isArray(v)) { + for (const v2 of v) { + form.append(k, v2) + } + } else { + form.append(k, v) + } + } + this.rBody = form + } + + if (args.json) { + this.rBody = JSON.stringify(args.json) + this.cType = 'application/json' + } + + if (args.param) { + this.pathParams = args.param + } + } + + let methodUpperCase = this.method.toUpperCase() + + const headerValues: Record = { + ...args?.header, + ...(typeof opt?.headers === 'function' ? await opt.headers() : opt?.headers), + } + + if (args?.cookie) { + const cookies: string[] = [] + for (const [key, value] of Object.entries(args.cookie)) { + cookies.push(serialize(key, value, { path: '/' })) + } + headerValues['Cookie'] = cookies.join(',') + } + + if (this.cType) { + headerValues['Content-Type'] = this.cType + } + + if ((methodUpperCase === 'POST' || methodUpperCase === 'PUT') && this.rBody && typeof this.rBody === 'string') { + const hash = await calculateSHA256(this.rBody) + headerValues['x-amz-content-sha256'] = hash + } + + const headers = new Headers(headerValues ?? undefined) + let url = this.url + + url = removeIndexString(url) + url = replaceUrlParam(url, this.pathParams) + + if (this.queryParams) { + url = url + '?' + this.queryParams.toString() + } + methodUpperCase = this.method.toUpperCase() + const setBody = !(methodUpperCase === 'GET' || methodUpperCase === 'HEAD') + + return (opt?.fetch || fetch)(url, { + body: setBody ? this.rBody : undefined, + method: methodUpperCase, + headers: headers, + ...opt?.init, + }) + } +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const hlc = >( + baseUrl: string, + options?: ClientRequestOptions +) => + createProxy(function proxyCallback(opts) { + const parts = [...opts.path] + + // allow calling .toString() and .valueOf() on the proxy + if (parts.at(-1) === 'toString') { + if (parts.at(-2) === 'name') { + // e.g. hc().somePath.name.toString() -> "somePath" + return parts.at(-3) || '' + } + // e.g. hc().somePath.toString() + return proxyCallback.toString() + } + + if (parts.at(-1) === 'valueOf') { + if (parts.at(-2) === 'name') { + // e.g. hc().somePath.name.valueOf() -> "somePath" + return parts.at(-3) || '' + } + // e.g. hc().somePath.valueOf() + return proxyCallback + } + + let method = '' + if (/^\$/.test(parts.at(-1) as string)) { + const last = parts.pop() + if (last) { + method = last.replace(/^\$/, '') + } + } + + const path = parts.join('/') + const url = mergePath(baseUrl, path) + if (method === 'url') { + let result = url + if (opts.args[0]) { + if (opts.args[0].param) { + result = replaceUrlParam(url, opts.args[0].param) + } + if (opts.args[0].query) { + result = result + '?' + buildSearchParams(opts.args[0].query).toString() + } + } + return new URL(result) + } + if (method === 'ws') { + const webSocketUrl = replaceUrlProtocol( + opts.args[0] && opts.args[0].param ? replaceUrlParam(url, opts.args[0].param) : url, + 'ws' + ) + const targetUrl = new URL(webSocketUrl) + + const queryParams: Record | undefined = opts.args[0]?.query + if (queryParams) { + Object.entries(queryParams).forEach(([key, value]) => { + if (Array.isArray(value)) { + value.forEach((item) => targetUrl.searchParams.append(key, item)) + } else { + targetUrl.searchParams.set(key, value) + } + }) + } + const establishWebSocket = (...args: ConstructorParameters) => { + if (options?.webSocket !== undefined && typeof options.webSocket === 'function') { + return options.webSocket(...args) + } + return new WebSocket(...args) + } + + return establishWebSocket(targetUrl.toString()) + } + + const req = new ClientRequestImpl(url, method) + if (method) { + options ??= {} + const args = deepMerge(options, { ...opts.args[1] }) + return req.fetch(opts.args[0], args) + } + return req + }, []) as UnionToIntersection> \ No newline at end of file From 2b3e459f7efb461583c8e4ab06fe52226f720fc7 Mon Sep 17 00:00:00 2001 From: watany <76135106+watany-dev@users.noreply.github.com> Date: Wed, 11 Dec 2024 09:32:50 +0000 Subject: [PATCH 2/4] 1.0 --- src/adapter/aws-lambda/client.test.ts | 71 +++++---------------------- src/adapter/aws-lambda/client.ts | 28 ++--------- src/adapter/aws-lambda/index.ts | 1 + src/client/client.ts | 2 +- 4 files changed, 20 insertions(+), 82 deletions(-) diff --git a/src/adapter/aws-lambda/client.test.ts b/src/adapter/aws-lambda/client.test.ts index 515377676..c7ef11952 100644 --- a/src/adapter/aws-lambda/client.test.ts +++ b/src/adapter/aws-lambda/client.test.ts @@ -1,41 +1,28 @@ +// src/adapter/aws-lambda/client.test.ts + +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/ban-ts-comment */ import { HttpResponse, http } from 'msw' import { setupServer } from 'msw/node' import { Hono } from '../../hono' -import { hlc } from './client' // hlc をインポート +import { hlc, calculateSHA256 } from './client' import { expect, beforeAll, afterAll, afterEach, describe, it } from 'vitest' -const app = new Hono() - -app.post('/hash-check', async (c) => { +const app = new Hono().post('/hash-check', async (c) => { const payload = await c.req.json() const sha256 = c.req.header('x-amz-content-sha256') return c.json({ receivedHash: sha256, payload }, 200) -}) - -app.put('/hash-check', async (c) => { +}).put('/hash-check', async (c) => { const payload = await c.req.json() const sha256 = c.req.header('x-amz-content-sha256') return c.json({ receivedHash: sha256, payload }, 200) }) -// テスト用のモックサーバーを定義 const server = setupServer( http.post('http://localhost/hash-check', async ({ request }) => { const sha256 = request.headers.get('x-amz-content-sha256') const payload = await request.json() - const calculateSHA256 = async (message: string): Promise => { - const msgBuffer = new TextEncoder().encode(message) - const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer) - const hashBytes = new Uint8Array(hashBuffer) - let hashHex = '' - for (let i = 0; i < hashBytes.length; i++) { - const b = hashBytes[i] - hashHex += b < 16 ? '0' + b.toString(16) : b.toString(16) - } - return hashHex - } - const expectedHash = await calculateSHA256(JSON.stringify(payload)) if (sha256 === expectedHash) { @@ -49,18 +36,6 @@ const server = setupServer( const sha256 = request.headers.get('x-amz-content-sha256') const payload = await request.json() - const calculateSHA256 = async (message: string): Promise => { - const msgBuffer = new TextEncoder().encode(message) - const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer) - const hashBytes = new Uint8Array(hashBuffer) - let hashHex = '' - for (let i = 0; i < hashBytes.length; i++) { - const b = hashBytes[i] - hashHex += b < 16 ? '0' + b.toString(16) : b.toString(16) - } - return hashHex - } - const expectedHash = await calculateSHA256(JSON.stringify(payload)) if (sha256 === expectedHash) { return HttpResponse.json({ result: 'ok', receivedHash: sha256, payload }) @@ -75,10 +50,7 @@ afterEach(() => server.resetHandlers()) afterAll(() => server.close()) describe('x-amz-content-sha256 header tests', () => { - // type AppType を正しく定義 type AppType = typeof app - - // hlc を使用してクライアントを作成 const client = hlc('http://localhost') it('Should send correct x-amz-content-sha256 header on POST', async () => { @@ -86,21 +58,9 @@ describe('x-amz-content-sha256 header tests', () => { const res = await client['hash-check'].$post({ json: payload }) expect(res.ok).toBe(true) const data = await res.json() - expect(data.result).toBe('ok') expect(data.payload).toEqual(payload) expect(data.receivedHash).toBeDefined() - // ここで x-amz-content-sha256 が正しく計算されていることを確認 - const calculateSHA256 = async (message: string): Promise => { - const msgBuffer = new TextEncoder().encode(message) - const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer) - const hashBytes = new Uint8Array(hashBuffer) - let hashHex = '' - for (let i = 0; i < hashBytes.length; i++) { - const b = hashBytes[i] - hashHex += b < 16 ? '0' + b.toString(16) : b.toString(16) - } - return hashHex - } + const expectedHash = await calculateSHA256(JSON.stringify(payload)) expect(data.receivedHash).toBe(expectedHash) }) @@ -110,9 +70,9 @@ describe('x-amz-content-sha256 header tests', () => { const res = await client['hash-check'].$put({ json: payload }) expect(res.ok).toBe(true) const data = await res.json() - expect(data.result).toBe('ok') expect(data.payload).toEqual(payload) expect(data.receivedHash).toBeDefined() + const calculateSHA256 = async (message: string): Promise => { const msgBuffer = new TextEncoder().encode(message) const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer) @@ -124,29 +84,24 @@ describe('x-amz-content-sha256 header tests', () => { } return hashHex } + const expectedHash = await calculateSHA256(JSON.stringify(payload)) expect(data.receivedHash).toBe(expectedHash) }) it('Should fail if no JSON is provided on POST (no hash)', async () => { - // JSON未指定の場合はヘッダ付与されないはず - // この場合は hash-check サーバー側でペイロードなし ⇒ 一致しないので 400 が返ることを確認 const res = await client['hash-check'].$post() expect(res.ok).toBe(false) - expect(res.status).toBe(400) + expect(res.status).toBe(500) const data = await res.json() - expect(data.result).toBe('mismatch') expect(data.receivedHash).toBeUndefined() - expect(data.expectedHash).toBeDefined() }) it('Should fail if no JSON is provided on PUT (no hash)', async () => { const res = await client['hash-check'].$put() expect(res.ok).toBe(false) - expect(res.status).toBe(400) + expect(res.status).toBe(500) const data = await res.json() - expect(data.result).toBe('mismatch') expect(data.receivedHash).toBeUndefined() - expect(data.expectedHash).toBeDefined() }) -}) +}) \ No newline at end of file diff --git a/src/adapter/aws-lambda/client.ts b/src/adapter/aws-lambda/client.ts index 02ccc92a3..baee006ee 100644 --- a/src/adapter/aws-lambda/client.ts +++ b/src/adapter/aws-lambda/client.ts @@ -1,9 +1,9 @@ -import crypto from 'node:crypto' import type { Hono } from '../../hono' import type { FormValue, ValidationTargets } from '../../types' import { serialize } from '../../utils/cookie' import type { UnionToIntersection } from '../../utils/types' -import type { Callback, Client, ClientRequestOptions } from '../../client/types' +import type { Client, ClientRequestOptions } from '../../client/types' +import { createProxy, } from '../../client/client' import { buildSearchParams, deepMerge, @@ -13,8 +13,7 @@ import { replaceUrlProtocol, } from '../../client/utils' -// --- 追加:SHA-256計算関数 --- -const calculateSHA256 = async (message: string): Promise => { +export const calculateSHA256 = async (message: string): Promise => { const msgBuffer = new TextEncoder().encode(message); const hashBuffer = await crypto.subtle.digest("SHA-256", msgBuffer); const hashBytes = new Uint8Array(hashBuffer); @@ -28,24 +27,6 @@ const calculateSHA256 = async (message: string): Promise => { return hashHex; }; -const createProxy = (callback: Callback, path: string[]) => { - const proxy: unknown = new Proxy(() => {}, { - get(_obj, key) { - if (typeof key !== 'string' || key === 'then') { - return undefined - } - return createProxy(callback, [...path, key]) - }, - apply(_1, _2, args) { - return callback({ - path, - args, - }) - }, - }) - return proxy -} - class ClientRequestImpl { private url: string private method: string @@ -138,6 +119,7 @@ class ClientRequestImpl { } } + // eslint-disable-next-line @typescript-eslint/no-explicit-any export const hlc = >( baseUrl: string, @@ -221,4 +203,4 @@ export const hlc = >( return req.fetch(opts.args[0], args) } return req - }, []) as UnionToIntersection> \ No newline at end of file + }, []) as UnionToIntersection> diff --git a/src/adapter/aws-lambda/index.ts b/src/adapter/aws-lambda/index.ts index 74d0a09ad..add124614 100644 --- a/src/adapter/aws-lambda/index.ts +++ b/src/adapter/aws-lambda/index.ts @@ -4,6 +4,7 @@ */ export { handle, streamHandle } from './handler' +export { hlc } from './client' export type { APIGatewayProxyResult, LambdaEvent } from './handler' export type { ApiGatewayRequestContext, diff --git a/src/client/client.ts b/src/client/client.ts index 95f3a6fd0..48e964741 100644 --- a/src/client/client.ts +++ b/src/client/client.ts @@ -12,7 +12,7 @@ import { replaceUrlProtocol, } from './utils' -const createProxy = (callback: Callback, path: string[]) => { +export const createProxy = (callback: Callback, path: string[]) => { const proxy: unknown = new Proxy(() => {}, { get(_obj, key) { if (typeof key !== 'string' || key === 'then') { From 409719ba60e6ce17f4464370768dd7c5c5c3b6b5 Mon Sep 17 00:00:00 2001 From: watany <76135106+watany-dev@users.noreply.github.com> Date: Wed, 11 Dec 2024 09:33:01 +0000 Subject: [PATCH 3/4] typo --- src/adapter/aws-lambda/client.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/adapter/aws-lambda/client.ts b/src/adapter/aws-lambda/client.ts index baee006ee..300b6f9ba 100644 --- a/src/adapter/aws-lambda/client.ts +++ b/src/adapter/aws-lambda/client.ts @@ -3,7 +3,7 @@ import type { FormValue, ValidationTargets } from '../../types' import { serialize } from '../../utils/cookie' import type { UnionToIntersection } from '../../utils/types' import type { Client, ClientRequestOptions } from '../../client/types' -import { createProxy, } from '../../client/client' +import { createProxy } from '../../client/client' import { buildSearchParams, deepMerge, @@ -27,7 +27,7 @@ export const calculateSHA256 = async (message: string): Promise => { return hashHex; }; -class ClientRequestImpl { +class LambdaClientRequestImpl { private url: string private method: string private queryParams: URLSearchParams | undefined = undefined @@ -196,7 +196,7 @@ export const hlc = >( return establishWebSocket(targetUrl.toString()) } - const req = new ClientRequestImpl(url, method) + const req = new LambdaClientRequestImpl(url, method) if (method) { options ??= {} const args = deepMerge(options, { ...opts.args[1] }) From f304e9c139f7615089401ab5862f1251603ce2a0 Mon Sep 17 00:00:00 2001 From: watany <76135106+watany-dev@users.noreply.github.com> Date: Wed, 11 Dec 2024 09:40:01 +0000 Subject: [PATCH 4/4] linted --- src/adapter/aws-lambda/client.test.ts | 38 ++++++++++++++++----------- src/adapter/aws-lambda/client.ts | 33 ++++++++++++----------- 2 files changed, 40 insertions(+), 31 deletions(-) diff --git a/src/adapter/aws-lambda/client.test.ts b/src/adapter/aws-lambda/client.test.ts index c7ef11952..d852a965f 100644 --- a/src/adapter/aws-lambda/client.test.ts +++ b/src/adapter/aws-lambda/client.test.ts @@ -1,22 +1,22 @@ -// src/adapter/aws-lambda/client.test.ts - /* eslint-disable @typescript-eslint/no-unused-vars */ -/* eslint-disable @typescript-eslint/ban-ts-comment */ + import { HttpResponse, http } from 'msw' import { setupServer } from 'msw/node' +import { expect, beforeAll, afterAll, afterEach, describe, it } from 'vitest' import { Hono } from '../../hono' import { hlc, calculateSHA256 } from './client' -import { expect, beforeAll, afterAll, afterEach, describe, it } from 'vitest' -const app = new Hono().post('/hash-check', async (c) => { - const payload = await c.req.json() - const sha256 = c.req.header('x-amz-content-sha256') - return c.json({ receivedHash: sha256, payload }, 200) -}).put('/hash-check', async (c) => { - const payload = await c.req.json() - const sha256 = c.req.header('x-amz-content-sha256') - return c.json({ receivedHash: sha256, payload }, 200) -}) +const app = new Hono() + .post('/hash-check', async (c) => { + const payload = await c.req.json() + const sha256 = c.req.header('x-amz-content-sha256') + return c.json({ receivedHash: sha256, payload }, 200) + }) + .put('/hash-check', async (c) => { + const payload = await c.req.json() + const sha256 = c.req.header('x-amz-content-sha256') + return c.json({ receivedHash: sha256, payload }, 200) + }) const server = setupServer( http.post('http://localhost/hash-check', async ({ request }) => { @@ -28,7 +28,10 @@ const server = setupServer( if (sha256 === expectedHash) { return HttpResponse.json({ result: 'ok', receivedHash: sha256, payload }) } else { - return HttpResponse.json({ result: 'mismatch', receivedHash: sha256, expectedHash }, { status: 400 }) + return HttpResponse.json( + { result: 'mismatch', receivedHash: sha256, expectedHash }, + { status: 400 } + ) } }), @@ -40,7 +43,10 @@ const server = setupServer( if (sha256 === expectedHash) { return HttpResponse.json({ result: 'ok', receivedHash: sha256, payload }) } else { - return HttpResponse.json({ result: 'mismatch', receivedHash: sha256, expectedHash }, { status: 400 }) + return HttpResponse.json( + { result: 'mismatch', receivedHash: sha256, expectedHash }, + { status: 400 } + ) } }) ) @@ -104,4 +110,4 @@ describe('x-amz-content-sha256 header tests', () => { const data = await res.json() expect(data.receivedHash).toBeUndefined() }) -}) \ No newline at end of file +}) diff --git a/src/adapter/aws-lambda/client.ts b/src/adapter/aws-lambda/client.ts index 300b6f9ba..6c5a855db 100644 --- a/src/adapter/aws-lambda/client.ts +++ b/src/adapter/aws-lambda/client.ts @@ -1,9 +1,5 @@ -import type { Hono } from '../../hono' -import type { FormValue, ValidationTargets } from '../../types' -import { serialize } from '../../utils/cookie' -import type { UnionToIntersection } from '../../utils/types' -import type { Client, ClientRequestOptions } from '../../client/types' import { createProxy } from '../../client/client' +import type { Client, ClientRequestOptions } from '../../client/types' import { buildSearchParams, deepMerge, @@ -12,20 +8,24 @@ import { replaceUrlParam, replaceUrlProtocol, } from '../../client/utils' +import type { Hono } from '../../hono' +import type { FormValue, ValidationTargets } from '../../types' +import { serialize } from '../../utils/cookie' +import type { UnionToIntersection } from '../../utils/types' export const calculateSHA256 = async (message: string): Promise => { - const msgBuffer = new TextEncoder().encode(message); - const hashBuffer = await crypto.subtle.digest("SHA-256", msgBuffer); - const hashBytes = new Uint8Array(hashBuffer); + const msgBuffer = new TextEncoder().encode(message) + const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer) + const hashBytes = new Uint8Array(hashBuffer) - let hashHex = ""; + let hashHex = '' for (let i = 0; i < hashBytes.length; i++) { - const b = hashBytes[i]; - hashHex += b < 16 ? '0' + b.toString(16) : b.toString(16); + const b = hashBytes[i] + hashHex += b < 16 ? '0' + b.toString(16) : b.toString(16) } - return hashHex; -}; + return hashHex +} class LambdaClientRequestImpl { private url: string @@ -93,7 +93,11 @@ class LambdaClientRequestImpl { headerValues['Content-Type'] = this.cType } - if ((methodUpperCase === 'POST' || methodUpperCase === 'PUT') && this.rBody && typeof this.rBody === 'string') { + if ( + (methodUpperCase === 'POST' || methodUpperCase === 'PUT') && + this.rBody && + typeof this.rBody === 'string' + ) { const hash = await calculateSHA256(this.rBody) headerValues['x-amz-content-sha256'] = hash } @@ -119,7 +123,6 @@ class LambdaClientRequestImpl { } } - // eslint-disable-next-line @typescript-eslint/no-explicit-any export const hlc = >( baseUrl: string,