148 lines
4.0 KiB
TypeScript
148 lines
4.0 KiB
TypeScript
import { getRequestListener } from "@hono/node-server";
|
|
import { Hono } from "hono";
|
|
import { cors } from "hono/cors";
|
|
import { vercelWebhookRouter } from "./external/vercel/vercelWebhookRouter.js";
|
|
import { handleConnectWebhook } from "./external/webhooks/connectWebhookRouter.js";
|
|
import { baseMiddleware } from "./honoMiddlewares/baseMiddleware.js";
|
|
import { errorMiddleware } from "./honoMiddlewares/errorMiddleware.js";
|
|
import { traceMiddleware } from "./honoMiddlewares/traceMiddleware.js";
|
|
import type { HonoEnv } from "./honoUtils/HonoEnv.js";
|
|
import { handleHealthCheck } from "./honoUtils/handleHealthCheck.js";
|
|
import { handleOAuthCallback } from "./internal/orgs/handlers/stripeHandlers/handleOAuthCallback.js";
|
|
import { apiRouter } from "./routers/apiRouter.js";
|
|
import { internalRouter } from "./routers/internalRouter.js";
|
|
import { auth } from "./utils/auth.js";
|
|
|
|
const ALLOWED_ORIGINS = [
|
|
"http://localhost:3000",
|
|
"http://localhost:5173",
|
|
"http://localhost:5174",
|
|
"https://app.useautumn.com",
|
|
"https://staging.useautumn.com",
|
|
"https://api.staging.useautumn.com",
|
|
"https://localhost:8080",
|
|
];
|
|
|
|
const ALLOWED_HEADERS = [
|
|
"app_env",
|
|
"x-api-version",
|
|
"x-client-type",
|
|
"Authorization",
|
|
"Content-Type",
|
|
"Accept",
|
|
"Origin",
|
|
"X-API-Version",
|
|
"X-Requested-With",
|
|
"Access-Control-Request-Method",
|
|
"Access-Control-Request-Headers",
|
|
"Cache-Control",
|
|
"If-Match",
|
|
"If-None-Match",
|
|
"If-Modified-Since",
|
|
"If-Unmodified-Since",
|
|
];
|
|
|
|
export const createHonoApp = () => {
|
|
const app = new Hono<HonoEnv>();
|
|
|
|
// CORS configuration (must be before routes)
|
|
app.use(
|
|
"*",
|
|
cors({
|
|
origin: ALLOWED_ORIGINS,
|
|
allowHeaders: ALLOWED_HEADERS,
|
|
allowMethods: ["POST", "GET", "PUT", "DELETE", "PATCH", "OPTIONS"],
|
|
exposeHeaders: ["Content-Length"],
|
|
maxAge: 600,
|
|
credentials: true,
|
|
}),
|
|
);
|
|
|
|
// Better Auth handler
|
|
app.on(["POST", "GET"], "/api/auth/*", (c) => {
|
|
return auth.handler(c.req.raw);
|
|
});
|
|
|
|
// OAuth callback (needs to be before middleware)
|
|
// Health check endpoint for AWS/ECS load balancer
|
|
|
|
app.get("/stripe/oauth_callback", handleOAuthCallback);
|
|
|
|
// Step 1: Base middleware - sets up ctx (db, logger, etc.)
|
|
app.use("*", baseMiddleware);
|
|
app.use("*", traceMiddleware);
|
|
|
|
app.get("/", handleHealthCheck);
|
|
|
|
// Add Render region identifier header for load balancer verification
|
|
app.use("*", async (c, next) => {
|
|
await next();
|
|
c.header("x-region", process.env.AWS_REGION);
|
|
});
|
|
|
|
// Webhook routes
|
|
app.post("/webhooks/connect/:env", handleConnectWebhook);
|
|
app.route("/webhooks/vercel", vercelWebhookRouter);
|
|
|
|
// API Middleware
|
|
app.route("/v1", apiRouter);
|
|
app.route("", internalRouter);
|
|
|
|
app.onError(errorMiddleware);
|
|
|
|
// Create request listener for integration with Express
|
|
const requestListener = getRequestListener(app.fetch);
|
|
return { honoApp: app, requestListener };
|
|
};
|
|
|
|
/**
|
|
* Smart middleware that checks if Hono has a matching route.
|
|
* If yes: forwards the request to Hono (fresh, unmodified)
|
|
* If no: calls next() to continue Express flow (untouched)
|
|
*/
|
|
export const redirectToHono = () => {
|
|
const { honoApp, requestListener } = createHonoApp();
|
|
|
|
// Get all routes from Hono app
|
|
const routes = honoApp.routes;
|
|
|
|
return async (req: any, res: any, next: any) => {
|
|
const method = req.method;
|
|
const path = req.path;
|
|
|
|
// Check if Hono has a matching route for this method + path
|
|
const hasMatch = routes.some((route) => {
|
|
// Check if method matches
|
|
if (route.method !== method && route.method !== "ALL") {
|
|
return false;
|
|
}
|
|
|
|
// Check if path matches (handle dynamic routes)
|
|
const routePath = route.path;
|
|
|
|
// Exact match
|
|
if (routePath === path) {
|
|
return true;
|
|
}
|
|
|
|
// Check for dynamic routes (e.g., /v1/products/:id)
|
|
if (routePath.includes(":")) {
|
|
const routeRegex = new RegExp(
|
|
`^${routePath.replace(/:[^/]+/g, "([^/]+)")}$`,
|
|
);
|
|
return routeRegex.test(path);
|
|
}
|
|
|
|
return false;
|
|
});
|
|
|
|
if (hasMatch) {
|
|
// Route exists in Hono - forward the FRESH request
|
|
return requestListener(req, res);
|
|
}
|
|
|
|
// No match - continue to Express (completely untouched)
|
|
next();
|
|
};
|
|
};
|