@erebus-sh/sdk
Version:
To install dependencies:
1 lines • 13.3 kB
Source Map (JSON)
{"version":3,"file":"next-Cn-qbSl3.cjs","names":["z","MessageBodySchema"],"sources":["../../schemas/webhooks/fireWebhook.ts","../../shared/utils/hmac.ts","../src/server/app.ts","../src/server/adapter/genericAdapter.ts","../src/server/adapter/next/createRouteHandler.ts"],"sourcesContent":["import { z } from \"zod\";\nimport { MessageBodySchema } from \"@repo/schemas/messageBody\";\n\nexport const FireWebhookSchema = z.object({\n messageBody: z.array(MessageBodySchema),\n hmac: z.string(),\n});\n\nexport type FireWebhookSchema = z.infer<typeof FireWebhookSchema>;\n","export async function generateHmac(\n payload: string,\n secret: string,\n): Promise<string> {\n // Encode the secret and payload as Uint8Array\n const enc = new TextEncoder();\n const keyData = enc.encode(secret);\n const payloadData = enc.encode(payload);\n\n // Import the secret as a CryptoKey for HMAC\n const cryptoKey = await crypto.subtle.importKey(\n \"raw\",\n keyData,\n { name: \"HMAC\", hash: { name: \"SHA-256\" } },\n false,\n [\"sign\"],\n );\n\n // Sign the payload\n const signature = await crypto.subtle.sign(\"HMAC\", cryptoKey, payloadData);\n\n // Convert ArrayBuffer to hex string\n return Array.from(new Uint8Array(signature))\n .map((b) => b.toString(16).padStart(2, \"0\"))\n .join(\"\");\n}\n\nexport async function verifyHmac(\n payload: string,\n secret: string,\n hmac: string,\n): Promise<boolean> {\n const generated = await generateHmac(payload, secret);\n return generated === hmac;\n}\n","import { Hono } from \"hono\";\nimport { logger } from \"@/internal/logger/consola\";\n\nimport { ErebusSession } from \"@/service/session\";\nimport { zValidator } from \"@hono/zod-validator\";\nimport { z } from \"zod\";\nimport { FireWebhookSchema } from \"@repo/schemas/webhooks/fireWebhook\";\nimport { verifyHmac } from \"@repo/shared/utils/hmac\";\n\nexport type AppVars = {\n reqId: string;\n session?: ErebusSession;\n};\n\ntype SessionProvider = (req: Request) => ErebusSession | Promise<ErebusSession>;\n\nexport function createApp(sessionOrProvider?: ErebusSession | SessionProvider) {\n const app = new Hono<{ Variables: AppVars }>();\n\n app.onError((err, c) => {\n logger.error(\"[unhandled]\", {\n reqId: c.get(\"reqId\"),\n err:\n err instanceof Error\n ? { name: err.name, message: err.message, stack: err.stack }\n : err,\n path: c.req.path,\n method: c.req.method,\n });\n return c.json({ error: \"internal_error\", reqId: c.get(\"reqId\") }, 500);\n });\n\n app.notFound((c) => {\n return c.json(\n { error: \"not_found\", path: c.req.path, reqId: c.get(\"reqId\") },\n 404,\n );\n });\n\n app.use(\"*\", async (c, next) => {\n /**\n * Generate a unique request id for the request to be identified by the logger\n */\n const reqId = crypto.randomUUID();\n c.set(\"reqId\", reqId);\n\n /**\n * Inject session if provided\n */\n if (sessionOrProvider) {\n if (typeof sessionOrProvider === \"function\") {\n // It's a session provider - call it per request\n const session = await sessionOrProvider(c.req.raw);\n c.set(\"session\", session);\n } else {\n // It's a static session (for route handler usage)\n c.set(\"session\", sessionOrProvider);\n }\n }\n\n /**\n * Log the request start time\n */\n const started = performance.now();\n try {\n await next();\n } finally {\n const ms = Math.round(performance.now() - started);\n logger.info(`[${reqId}] ${c.req.method} ${c.req.path} -> ${ms}ms`);\n }\n });\n\n // Add the routes using the shared routes function\n return app.route(\"/\", routes);\n}\n\n// Define routes separately for RPC type inference\nconst routes = new Hono<{ Variables: AppVars }>()\n /**\n * Health check route\n */\n .get(\"/api/health-not-meaningful\", (c) =>\n c.json({ ok: true, reqId: c.get(\"reqId\") }),\n )\n /**\n * Generate a token route test\n */\n .get(\"/api/generate-token-test\", (c) => {\n const session = c.get(\"session\");\n if (!session) {\n return c.json(\n {\n error: \"session_required, the server is not initialized properly\",\n reqId: c.get(\"reqId\"),\n },\n 400,\n );\n }\n\n console.log(session.__debugObject);\n\n logger.info(`[${c.get(\"reqId\")}] Generating token`);\n return c.json({\n token: \"test\",\n });\n })\n /**\n * This is the API to generate the token for the client\n * it calls Erebus service to generate the token\n */\n .post(\n \"/api/erebus/pubsub/grant\",\n zValidator(\n \"json\",\n z.object({\n channel: z.string(),\n }),\n ),\n async (c) => {\n const session = c.get(\"session\");\n const reqId = c.get(\"reqId\");\n logger.info(`[${reqId}] Generating token`);\n\n if (!session) {\n return c.json(\n {\n error: \"session_required, the server is not initialized properly\",\n reqId,\n },\n 400,\n );\n }\n let token = \"\"; // default value to avoid undefined\n try {\n token = await session.authorize();\n } catch (error) {\n logger.error(\n `[${reqId}] Error generating token: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n );\n return c.json(\n {\n error: \"error_generating_token\",\n reqId,\n message: error instanceof Error ? error.message : \"Unknown error\",\n },\n 500,\n );\n }\n\n return c.json({\n grant_jwt: token,\n });\n },\n )\n .post(\n \"/api/erebus/pubsub/fire-webhook\",\n zValidator(\"json\", FireWebhookSchema),\n async (c) => {\n const { messageBody, hmac } = c.req.valid(\"json\");\n const secret = process.env[\"WEBHOOK_SECRET\"];\n if (!secret) {\n console.error(\n \"[pubsub][fire-webhook] WEBHOOK_SECRET is not set please, set the secret key in the environment variables\",\n );\n return c.json(\n {\n error:\n \"WEBHOOK_SECRET is not set please, set the secret key in the environment variables\",\n },\n 500,\n );\n }\n if (!verifyHmac(JSON.stringify(messageBody), secret, hmac)) {\n console.error(\n \"[pubsub][fire-webhook] Invalid HMAC please, check the HMAC is correct\",\n );\n return c.json(\n {\n error: \"Invalid HMAC please, check the HMAC is correct\",\n },\n 401,\n );\n }\n console.log(\n \"[pubsub][fire-webhook] HMAC verified, proceeding to next middleware\",\n );\n return c.json({\n ok: true,\n });\n },\n );\n\n// Export the AppType for RPC client usage\nexport type AppType = typeof routes;\n\nexport type AuthorizeServer = (\n req: Request,\n) => ErebusSession | Promise<ErebusSession>;\n\n// export const startAuthServer = async (\n// port: number,\n// authorize: AuthorizeServer,\n// ) => {\n// const app = createApp(authorize);\n// logger.info(`Attempting to start server on port ${port}...`);\n// const server = serve({\n// fetch: app.fetch,\n// port,\n// });\n// server.on(\"listening\", () => {\n// logger.info(`Server successfully started and is running on port ${port}`);\n// });\n// server.on(\"error\", (err: Error) => {\n// logger.error(`Server failed to start on port ${port}: ${err.message}`);\n// });\n// return server;\n// };\n","import { createApp } from \"@/server/app\";\nimport type { ErebusSession } from \"@/service/session\";\nimport type { FireWebhookSchema } from \"@repo/schemas/webhooks/fireWebhook\";\n\nexport type Authorize = (\n channel: string,\n ctx: { req: Request },\n) => ErebusSession | Promise<ErebusSession>;\n\nexport type FireWebhook = (webHookMessage: FireWebhookSchema) => Promise<void>;\n\nexport async function getSessionFromRequest(\n req: Request,\n authorize: Authorize,\n fireWebhook: FireWebhook,\n): Promise<ErebusSession | undefined> {\n let channel = \"\";\n\n try {\n const body = (await req.clone().json()) as { channel?: unknown };\n if (typeof body.channel === \"string\") {\n channel = body.channel;\n }\n } catch {\n // If parsing fails, channel remains empty\n }\n\n // Check if this is a webhook request\n const isWebhookRequest =\n req.method === \"POST\" &&\n (req.url === \"/api/erebus/pubsub/fire-webhook\" ||\n req.url.endsWith(\"/api/erebus/pubsub/fire-webhook\"));\n\n if (isWebhookRequest) {\n const webHookMessage = await req.json();\n await fireWebhook(webHookMessage);\n return undefined;\n } else {\n return await authorize(channel, { req });\n }\n}\n\nexport function createAdapter({\n authorize,\n fireWebhook,\n}: {\n authorize: Authorize;\n fireWebhook: FireWebhook;\n}) {\n const fetch = async (req: Request): Promise<Response> => {\n const session = await getSessionFromRequest(req, authorize, fireWebhook);\n // Create a new app instance with the session injected\n const app = createApp(session);\n\n return await app.fetch(req);\n };\n\n return {\n fetch,\n };\n}\n","import { handle } from \"hono/vercel\";\nimport { createApp } from \"@/server/app\";\nimport { getSessionFromRequest } from \"../genericAdapter\";\nimport type { Authorize, FireWebhook } from \"../genericAdapter\";\n\nexport function createRouteHandler({\n authorize,\n fireWebhook,\n}: {\n authorize: Authorize;\n fireWebhook: FireWebhook;\n}) {\n const createHandler = async (req: Request): Promise<Response> => {\n const session = await getSessionFromRequest(req, authorize, fireWebhook);\n // Create a new app instance with the session injected\n const app = createApp(session);\n\n const h = handle(app);\n return await h(req);\n };\n\n return {\n POST: createHandler,\n };\n}\n"],"mappings":"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"}