UNPKG

hono-firebase-functions

Version:

Run Hono apps on Firebase Functions using a simple adapter that bridges Firebase’s HTTP layer with Hono’s fetch API.

160 lines (137 loc) 5.78 kB
import type { Response as ExpressResponse } from 'express'; import { Request as FirebaseRequest, CallableRequest, } from 'firebase-functions/v2/https'; import type { CloudEvent } from 'firebase-functions/v2'; // 必要に応じて ScheduledEvent 等もimport import { Hono } from 'hono'; import { assembleRequest } from './assembleRequest'; import { mergeHeaders } from './mergeHeaders'; // import type { IncomingHttpHeaders } from 'http' /** * Firebase Functions 用メソッドの種類 */ type FirebaseFunctionType = 'onRequest' | 'onCall' | 'onEvent' | 'onSchedule'; /** * それぞれが返すハンドラの型 */ type OnRequestHandler = (req: FirebaseRequest, res: ExpressResponse) => Promise<void>; type OnCallHandler = (req: CallableRequest<unknown>) => any; type OnEventHandler<T> = (event: CloudEvent<T>) => void | Promise<void>; type OnScheduleHandler = () => void | Promise<void>; /** * オーバーロード定義 * 第3引数 templateRequest は省略可。 */ function handle<T = unknown>(app: Hono): OnRequestHandler; function handle<T = unknown>(app: Hono, type: 'onRequest', templateRequest?: Partial<Request>): OnRequestHandler; function handle<T = unknown>(app: Hono, type: 'onCall', templateRequest?: Partial<Request>): OnCallHandler; function handle<T = unknown>(app: Hono, type: 'onEvent', templateRequest?: Partial<Request>): OnEventHandler<T>; function handle<T = unknown>(app: Hono, type: 'onSchedule',templateRequest?: Partial<Request>): OnScheduleHandler; /** * 実装本体 */ function handle<T = unknown>( app: Hono, type: FirebaseFunctionType = 'onRequest', templateRequest?: Partial<Request> ): OnRequestHandler | OnCallHandler | OnEventHandler<T> | OnScheduleHandler { switch (type) { case 'onRequest': { // HTTPリクエスト対応 return async (req: FirebaseRequest, res: ExpressResponse) => { const protocol = req.headers['x-forwarded-proto'] ?? 'https'; const host = req.headers.host ?? 'localhost'; const url = `${protocol}://${host}${req.url}`; // テンプレートと実際のリクエストをマージして fetch Request を作る const init: RequestInit = { // テンプレートに method があれば優先し、なければ Firebase の req.method method: templateRequest?.method ?? req.method, // 同様にヘッダーもマージ headers: mergeHeaders(templateRequest?.headers, req.headers), }; // 必要に応じてリクエスト body も扱う // if (req.method !== 'GET' && req.method !== 'HEAD' && req.body) { // init.body = req.rawBody; // Buffer -> fetch Request の body // } // もし templateRequest?.body があるなら上書き if (templateRequest?.body) { init.body = templateRequest.body; } const fetchRequest = new Request(url, init); const fetchResponse = await app.fetch(fetchRequest); // Firebase側のレスポンスへ返却 res.status(fetchResponse.status); fetchResponse.headers.forEach((val, key) => { res.setHeader(key, val); }); const buffer = Buffer.from(await fetchResponse.arrayBuffer()); res.send(buffer); }; } case 'onCall': { // CallableRequest 対応 return async (callReq: CallableRequest<unknown>) => { // テンプレートの method/headers/body もマージ可能 const init: RequestInit = { body: templateRequest?.body ?? JSON.stringify(callReq.data), // デフォルトでは callReq.data を送る }; const fetchRequest = assembleRequest(templateRequest, init) const fetchResponse = await app.fetch(fetchRequest); // onCall では return した値がクライアントへ返される // ここでは JSON として返す例 return await fetchResponse.json(); }; } case 'onEvent': { // Firestoreトリガー等のクラウドイベント対応 return async <T>(event: CloudEvent<T>) => { const init: RequestInit = { body: templateRequest?.body ?? JSON.stringify(event), // 例: まるごと送る }; const fetchRequest = assembleRequest(templateRequest, init); const response = await app.fetch(fetchRequest); // イベントトリガーは戻り値があまり重要でないことが多いが // 必要に応じてログを出すなど console.log('onEvent response status:', response.status); }; } case 'onSchedule': { // スケジュールトリガー対応 return async () => { const init: RequestInit = { body: templateRequest?.body ?? JSON.stringify({ schedule: true }), }; const fetchRequest =assembleRequest(templateRequest, init); const response = await app.fetch(fetchRequest); console.log('onSchedule response status:', response.status); }; } default: throw new Error(`Unsupported Firebase function type: ${type}`); } } /** * NodeのIncomingHttpHeadersを、Fetch APIが受け取れるRecord<string, string>に変換する */ // function convertNodeHeadersToRecord( // incoming: IncomingHttpHeaders // ): Record<string, string> { // const result: Record<string, string> = {}; // for (const [key, val] of Object.entries(incoming)) { // if (typeof val === 'string') { // result[key] = val; // } else if (Array.isArray(val)) { // // 配列の場合は適当に結合 // result[key] = val.join(','); // } // // val が undefined の場合はスキップする // } // return result; // } export { handle };