@geekmidas/telescope 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/server/hono.cjs
CHANGED
|
@@ -28,11 +28,10 @@ function createMiddleware(telescope) {
|
|
|
28
28
|
"PATCH"
|
|
29
29
|
].includes(c.req.method)) try {
|
|
30
30
|
const contentType = c.req.header("content-type") || "";
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
} else if (contentType.includes("text/")) body = await c.req.text();
|
|
31
|
+
const clonedRequest = c.req.raw.clone();
|
|
32
|
+
if (contentType.includes("application/json")) body = await clonedRequest.json();
|
|
33
|
+
else if (contentType.includes("application/x-www-form-urlencoded")) body = Object.fromEntries((await clonedRequest.formData()).entries());
|
|
34
|
+
else if (contentType.includes("text/")) body = await clonedRequest.text();
|
|
36
35
|
} catch {}
|
|
37
36
|
const ip = c.req.header("x-forwarded-for") || c.req.header("x-real-ip");
|
|
38
37
|
try {
|
package/dist/server/hono.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hono.cjs","names":["telescope: Telescope","c: Context","next: Next","headers: Record<string, string>","query: Record<string, string>","body: unknown","responseHeaders: Record<string, string>","responseBody: unknown","Hono","app: Hono","upgradeWebSocket: (handler: any) => any","_event: Event","ws: WebSocket","event: MessageEvent"],"sources":["../../src/server/hono.ts"],"sourcesContent":["import { Hono } from 'hono';\nimport type { Context, MiddlewareHandler, Next } from 'hono';\nimport type { Telescope } from '../Telescope';\nimport type { QueryOptions } from '../types';\nimport { getAsset, getIndexHtml } from '../ui-assets';\n\nconst CONTEXT_KEY = 'telescope-request-id';\n\n/**\n * Create Hono middleware that captures requests and responses\n */\nexport function createMiddleware(telescope: Telescope): MiddlewareHandler {\n return async (c: Context, next: Next) => {\n if (!telescope.enabled) {\n return next();\n }\n\n if (telescope.shouldIgnore(c.req.path)) {\n return next();\n }\n\n const startTime = performance.now();\n\n // Capture request data\n const headers: Record<string, string> = {};\n c.req.raw.headers.forEach((value, key) => {\n headers[key] = value;\n });\n\n const url = new URL(c.req.url);\n const query: Record<string, string> = {};\n url.searchParams.forEach((value, key) => {\n query[key] = value;\n });\n\n let body: unknown;\n if (\n telescope.recordBody &&\n ['POST', 'PUT', 'PATCH'].includes(c.req.method)\n ) {\n try {\n const contentType = c.req.header('content-type') || '';\n if (contentType.includes('application/json')) {\n body = await c.req.json();\n } else if (contentType.includes('application/x-www-form-urlencoded')) {\n const formData = await c.req.formData();\n body = Object.fromEntries(formData.entries());\n } else if (contentType.includes('text/')) {\n body = await c.req.text();\n }\n } catch {\n // Ignore body parsing errors\n }\n }\n\n const ip = c.req.header('x-forwarded-for') || c.req.header('x-real-ip');\n\n try {\n await next();\n\n // Capture response data\n const duration = performance.now() - startTime;\n\n const responseHeaders: Record<string, string> = {};\n c.res.headers.forEach((value, key) => {\n responseHeaders[key] = value;\n });\n\n let responseBody: unknown;\n if (telescope.recordBody) {\n try {\n const contentType = c.res.headers.get('content-type') || '';\n if (contentType.includes('application/json')) {\n const cloned = c.res.clone();\n responseBody = await cloned.json();\n }\n } catch {\n // Ignore body parsing errors\n }\n }\n\n const requestId = await telescope.recordRequest({\n method: c.req.method,\n path: c.req.path,\n url: c.req.url,\n headers,\n body,\n query,\n status: c.res.status,\n responseHeaders,\n responseBody,\n duration,\n ip,\n });\n\n c.set(CONTEXT_KEY, requestId);\n } catch (error) {\n await telescope.exception(error as Error);\n throw error;\n }\n };\n}\n\n/**\n * Parse query options from Hono context\n */\nfunction parseQueryOptions(c: Context): QueryOptions {\n const limit = parseInt(c.req.query('limit') || '50', 10);\n const offset = parseInt(c.req.query('offset') || '0', 10);\n const search = c.req.query('search');\n const before = c.req.query('before');\n const after = c.req.query('after');\n const tags = c.req.query('tags')?.split(',').filter(Boolean);\n const method = c.req.query('method');\n const status = c.req.query('status');\n const level = c.req.query('level') as\n | 'debug'\n | 'info'\n | 'warn'\n | 'error'\n | undefined;\n\n return {\n limit: Math.min(limit, 100),\n offset,\n search,\n before: before ? new Date(before) : undefined,\n after: after ? new Date(after) : undefined,\n tags,\n method: method || undefined,\n status: status || undefined,\n level: level || undefined,\n };\n}\n\n/**\n * Create Hono app with dashboard UI and API routes\n */\nexport function createUI(telescope: Telescope): Hono {\n const app = new Hono();\n\n // API routes\n app.get('/api/requests', async (c) => {\n const options = parseQueryOptions(c);\n const requests = await telescope.getRequests(options);\n return c.json(requests);\n });\n\n app.get('/api/requests/:id', async (c) => {\n const request = await telescope.getRequest(c.req.param('id'));\n if (!request) {\n return c.json({ error: 'Request not found' }, 404);\n }\n return c.json(request);\n });\n\n app.get('/api/exceptions', async (c) => {\n const options = parseQueryOptions(c);\n const exceptions = await telescope.getExceptions(options);\n return c.json(exceptions);\n });\n\n app.get('/api/exceptions/:id', async (c) => {\n const exception = await telescope.getException(c.req.param('id'));\n if (!exception) {\n return c.json({ error: 'Exception not found' }, 404);\n }\n return c.json(exception);\n });\n\n app.get('/api/logs', async (c) => {\n const options = parseQueryOptions(c);\n const logs = await telescope.getLogs(options);\n return c.json(logs);\n });\n\n app.get('/api/stats', async (c) => {\n const stats = await telescope.getStats();\n return c.json(stats);\n });\n\n // Static assets\n app.get('/assets/:filename', (c) => {\n const filename = c.req.param('filename');\n const assetPath = `assets/${filename}`;\n const asset = getAsset(assetPath);\n if (asset) {\n return c.body(asset.content, 200, {\n 'Content-Type': asset.contentType,\n 'Cache-Control': 'public, max-age=31536000, immutable',\n });\n }\n return c.notFound();\n });\n\n // Dashboard UI - serve React app\n app.get('/', (c) => {\n const html = getIndexHtml();\n if (!html) {\n return c.text(\n 'Telescope UI not available. Run \"pnpm build:ui\" first.',\n 500,\n );\n }\n return c.html(html);\n });\n\n app.get('/*', (c) => {\n // SPA fallback - serve index.html for client-side routing\n const html = getIndexHtml();\n if (!html) {\n return c.text(\n 'Telescope UI not available. Run \"pnpm build:ui\" first.',\n 500,\n );\n }\n return c.html(html);\n });\n\n return app;\n}\n\n/**\n * Set up WebSocket routes for real-time updates.\n * Requires @hono/node-ws for Node.js or Bun's built-in WebSocket.\n */\nexport function setupWebSocket(\n app: Hono,\n telescope: Telescope,\n upgradeWebSocket: (handler: any) => any,\n): void {\n app.get(\n '/ws',\n upgradeWebSocket(() => ({\n onOpen: (_event: Event, ws: WebSocket) => {\n telescope.addWsClient(ws);\n },\n onClose: (_event: Event, ws: WebSocket) => {\n telescope.removeWsClient(ws);\n },\n onMessage: (event: MessageEvent, ws: WebSocket) => {\n try {\n const data = JSON.parse(event.data);\n if (data.type === 'ping') {\n ws.send(JSON.stringify({ type: 'pong' }));\n }\n } catch {\n // Ignore invalid messages\n }\n },\n })),\n );\n}\n\n/**\n * Get the request ID from Hono context (set by middleware)\n */\nexport function getRequestId(c: Context): string | undefined {\n return c.get(CONTEXT_KEY);\n}\n\n// Re-export types\nexport type { Telescope };\n"],"mappings":";;;;;AAMA,MAAM,cAAc;;;;AAKpB,SAAgB,iBAAiBA,WAAyC;AACxE,QAAO,OAAOC,GAAYC,SAAe;AACvC,OAAK,UAAU,QACb,QAAO,MAAM;AAGf,MAAI,UAAU,aAAa,EAAE,IAAI,KAAK,CACpC,QAAO,MAAM;EAGf,MAAM,YAAY,YAAY,KAAK;EAGnC,MAAMC,UAAkC,CAAE;AAC1C,IAAE,IAAI,IAAI,QAAQ,QAAQ,CAAC,OAAO,QAAQ;AACxC,WAAQ,OAAO;EAChB,EAAC;EAEF,MAAM,MAAM,IAAI,IAAI,EAAE,IAAI;EAC1B,MAAMC,QAAgC,CAAE;AACxC,MAAI,aAAa,QAAQ,CAAC,OAAO,QAAQ;AACvC,SAAM,OAAO;EACd,EAAC;EAEF,IAAIC;AACJ,MACE,UAAU,cACV;GAAC;GAAQ;GAAO;EAAQ,EAAC,SAAS,EAAE,IAAI,OAAO,CAE/C,KAAI;GACF,MAAM,cAAc,EAAE,IAAI,OAAO,eAAe,IAAI;AACpD,OAAI,YAAY,SAAS,mBAAmB,CAC1C,QAAO,MAAM,EAAE,IAAI,MAAM;YAChB,YAAY,SAAS,oCAAoC,EAAE;IACpE,MAAM,WAAW,MAAM,EAAE,IAAI,UAAU;AACvC,WAAO,OAAO,YAAY,SAAS,SAAS,CAAC;GAC9C,WAAU,YAAY,SAAS,QAAQ,CACtC,QAAO,MAAM,EAAE,IAAI,MAAM;EAE5B,QAAO,CAEP;EAGH,MAAM,KAAK,EAAE,IAAI,OAAO,kBAAkB,IAAI,EAAE,IAAI,OAAO,YAAY;AAEvE,MAAI;AACF,SAAM,MAAM;GAGZ,MAAM,WAAW,YAAY,KAAK,GAAG;GAErC,MAAMC,kBAA0C,CAAE;AAClD,KAAE,IAAI,QAAQ,QAAQ,CAAC,OAAO,QAAQ;AACpC,oBAAgB,OAAO;GACxB,EAAC;GAEF,IAAIC;AACJ,OAAI,UAAU,WACZ,KAAI;IACF,MAAM,cAAc,EAAE,IAAI,QAAQ,IAAI,eAAe,IAAI;AACzD,QAAI,YAAY,SAAS,mBAAmB,EAAE;KAC5C,MAAM,SAAS,EAAE,IAAI,OAAO;AAC5B,oBAAe,MAAM,OAAO,MAAM;IACnC;GACF,QAAO,CAEP;GAGH,MAAM,YAAY,MAAM,UAAU,cAAc;IAC9C,QAAQ,EAAE,IAAI;IACd,MAAM,EAAE,IAAI;IACZ,KAAK,EAAE,IAAI;IACX;IACA;IACA;IACA,QAAQ,EAAE,IAAI;IACd;IACA;IACA;IACA;GACD,EAAC;AAEF,KAAE,IAAI,aAAa,UAAU;EAC9B,SAAQ,OAAO;AACd,SAAM,UAAU,UAAU,MAAe;AACzC,SAAM;EACP;CACF;AACF;;;;AAKD,SAAS,kBAAkBN,GAA0B;CACnD,MAAM,QAAQ,SAAS,EAAE,IAAI,MAAM,QAAQ,IAAI,MAAM,GAAG;CACxD,MAAM,SAAS,SAAS,EAAE,IAAI,MAAM,SAAS,IAAI,KAAK,GAAG;CACzD,MAAM,SAAS,EAAE,IAAI,MAAM,SAAS;CACpC,MAAM,SAAS,EAAE,IAAI,MAAM,SAAS;CACpC,MAAM,QAAQ,EAAE,IAAI,MAAM,QAAQ;CAClC,MAAM,OAAO,EAAE,IAAI,MAAM,OAAO,EAAE,MAAM,IAAI,CAAC,OAAO,QAAQ;CAC5D,MAAM,SAAS,EAAE,IAAI,MAAM,SAAS;CACpC,MAAM,SAAS,EAAE,IAAI,MAAM,SAAS;CACpC,MAAM,QAAQ,EAAE,IAAI,MAAM,QAAQ;AAOlC,QAAO;EACL,OAAO,KAAK,IAAI,OAAO,IAAI;EAC3B;EACA;EACA,QAAQ,SAAS,IAAI,KAAK;EAC1B,OAAO,QAAQ,IAAI,KAAK;EACxB;EACA,QAAQ;EACR,QAAQ;EACR,OAAO;CACR;AACF;;;;AAKD,SAAgB,SAASD,WAA4B;CACnD,MAAM,MAAM,IAAIQ;AAGhB,KAAI,IAAI,iBAAiB,OAAO,MAAM;EACpC,MAAM,UAAU,kBAAkB,EAAE;EACpC,MAAM,WAAW,MAAM,UAAU,YAAY,QAAQ;AACrD,SAAO,EAAE,KAAK,SAAS;CACxB,EAAC;AAEF,KAAI,IAAI,qBAAqB,OAAO,MAAM;EACxC,MAAM,UAAU,MAAM,UAAU,WAAW,EAAE,IAAI,MAAM,KAAK,CAAC;AAC7D,OAAK,QACH,QAAO,EAAE,KAAK,EAAE,OAAO,oBAAqB,GAAE,IAAI;AAEpD,SAAO,EAAE,KAAK,QAAQ;CACvB,EAAC;AAEF,KAAI,IAAI,mBAAmB,OAAO,MAAM;EACtC,MAAM,UAAU,kBAAkB,EAAE;EACpC,MAAM,aAAa,MAAM,UAAU,cAAc,QAAQ;AACzD,SAAO,EAAE,KAAK,WAAW;CAC1B,EAAC;AAEF,KAAI,IAAI,uBAAuB,OAAO,MAAM;EAC1C,MAAM,YAAY,MAAM,UAAU,aAAa,EAAE,IAAI,MAAM,KAAK,CAAC;AACjE,OAAK,UACH,QAAO,EAAE,KAAK,EAAE,OAAO,sBAAuB,GAAE,IAAI;AAEtD,SAAO,EAAE,KAAK,UAAU;CACzB,EAAC;AAEF,KAAI,IAAI,aAAa,OAAO,MAAM;EAChC,MAAM,UAAU,kBAAkB,EAAE;EACpC,MAAM,OAAO,MAAM,UAAU,QAAQ,QAAQ;AAC7C,SAAO,EAAE,KAAK,KAAK;CACpB,EAAC;AAEF,KAAI,IAAI,cAAc,OAAO,MAAM;EACjC,MAAM,QAAQ,MAAM,UAAU,UAAU;AACxC,SAAO,EAAE,KAAK,MAAM;CACrB,EAAC;AAGF,KAAI,IAAI,qBAAqB,CAAC,MAAM;EAClC,MAAM,WAAW,EAAE,IAAI,MAAM,WAAW;EACxC,MAAM,aAAa,SAAS,SAAS;EACrC,MAAM,QAAQ,2BAAS,UAAU;AACjC,MAAI,MACF,QAAO,EAAE,KAAK,MAAM,SAAS,KAAK;GAChC,gBAAgB,MAAM;GACtB,iBAAiB;EAClB,EAAC;AAEJ,SAAO,EAAE,UAAU;CACpB,EAAC;AAGF,KAAI,IAAI,KAAK,CAAC,MAAM;EAClB,MAAM,OAAO,gCAAc;AAC3B,OAAK,KACH,QAAO,EAAE,KACP,4DACA,IACD;AAEH,SAAO,EAAE,KAAK,KAAK;CACpB,EAAC;AAEF,KAAI,IAAI,MAAM,CAAC,MAAM;EAEnB,MAAM,OAAO,gCAAc;AAC3B,OAAK,KACH,QAAO,EAAE,KACP,4DACA,IACD;AAEH,SAAO,EAAE,KAAK,KAAK;CACpB,EAAC;AAEF,QAAO;AACR;;;;;AAMD,SAAgB,eACdC,KACAT,WACAU,kBACM;AACN,KAAI,IACF,OACA,iBAAiB,OAAO;EACtB,QAAQ,CAACC,QAAeC,OAAkB;AACxC,aAAU,YAAY,GAAG;EAC1B;EACD,SAAS,CAACD,QAAeC,OAAkB;AACzC,aAAU,eAAe,GAAG;EAC7B;EACD,WAAW,CAACC,OAAqBD,OAAkB;AACjD,OAAI;IACF,MAAM,OAAO,KAAK,MAAM,MAAM,KAAK;AACnC,QAAI,KAAK,SAAS,OAChB,IAAG,KAAK,KAAK,UAAU,EAAE,MAAM,OAAQ,EAAC,CAAC;GAE5C,QAAO,CAEP;EACF;CACF,GAAE,CACJ;AACF;;;;AAKD,SAAgB,aAAaX,GAAgC;AAC3D,QAAO,EAAE,IAAI,YAAY;AAC1B"}
|
|
1
|
+
{"version":3,"file":"hono.cjs","names":["telescope: Telescope","c: Context","next: Next","headers: Record<string, string>","query: Record<string, string>","body: unknown","responseHeaders: Record<string, string>","responseBody: unknown","Hono","app: Hono","upgradeWebSocket: (handler: any) => any","_event: Event","ws: WebSocket","event: MessageEvent"],"sources":["../../src/server/hono.ts"],"sourcesContent":["import { Hono } from 'hono';\nimport type { Context, MiddlewareHandler, Next } from 'hono';\nimport type { Telescope } from '../Telescope';\nimport type { QueryOptions } from '../types';\nimport { getAsset, getIndexHtml } from '../ui-assets';\n\nconst CONTEXT_KEY = 'telescope-request-id';\n\n/**\n * Create Hono middleware that captures requests and responses\n */\nexport function createMiddleware(telescope: Telescope): MiddlewareHandler {\n return async (c: Context, next: Next) => {\n if (!telescope.enabled) {\n return next();\n }\n\n if (telescope.shouldIgnore(c.req.path)) {\n return next();\n }\n\n const startTime = performance.now();\n\n // Capture request data\n const headers: Record<string, string> = {};\n c.req.raw.headers.forEach((value, key) => {\n headers[key] = value;\n });\n\n const url = new URL(c.req.url);\n const query: Record<string, string> = {};\n url.searchParams.forEach((value, key) => {\n query[key] = value;\n });\n\n let body: unknown;\n if (\n telescope.recordBody &&\n ['POST', 'PUT', 'PATCH'].includes(c.req.method)\n ) {\n try {\n const contentType = c.req.header('content-type') || '';\n // Clone the request to avoid consuming the body stream\n const clonedRequest = c.req.raw.clone();\n if (contentType.includes('application/json')) {\n body = await clonedRequest.json();\n } else if (contentType.includes('application/x-www-form-urlencoded')) {\n body = Object.fromEntries((await clonedRequest.formData()).entries());\n } else if (contentType.includes('text/')) {\n body = await clonedRequest.text();\n }\n } catch {\n // Ignore body parsing errors\n }\n }\n\n const ip = c.req.header('x-forwarded-for') || c.req.header('x-real-ip');\n\n try {\n await next();\n\n // Capture response data\n const duration = performance.now() - startTime;\n\n const responseHeaders: Record<string, string> = {};\n c.res.headers.forEach((value, key) => {\n responseHeaders[key] = value;\n });\n\n let responseBody: unknown;\n if (telescope.recordBody) {\n try {\n const contentType = c.res.headers.get('content-type') || '';\n if (contentType.includes('application/json')) {\n const cloned = c.res.clone();\n responseBody = await cloned.json();\n }\n } catch {\n // Ignore body parsing errors\n }\n }\n\n const requestId = await telescope.recordRequest({\n method: c.req.method,\n path: c.req.path,\n url: c.req.url,\n headers,\n body,\n query,\n status: c.res.status,\n responseHeaders,\n responseBody,\n duration,\n ip,\n });\n\n c.set(CONTEXT_KEY, requestId);\n } catch (error) {\n await telescope.exception(error as Error);\n throw error;\n }\n };\n}\n\n/**\n * Parse query options from Hono context\n */\nfunction parseQueryOptions(c: Context): QueryOptions {\n const limit = parseInt(c.req.query('limit') || '50', 10);\n const offset = parseInt(c.req.query('offset') || '0', 10);\n const search = c.req.query('search');\n const before = c.req.query('before');\n const after = c.req.query('after');\n const tags = c.req.query('tags')?.split(',').filter(Boolean);\n const method = c.req.query('method');\n const status = c.req.query('status');\n const level = c.req.query('level') as\n | 'debug'\n | 'info'\n | 'warn'\n | 'error'\n | undefined;\n\n return {\n limit: Math.min(limit, 100),\n offset,\n search,\n before: before ? new Date(before) : undefined,\n after: after ? new Date(after) : undefined,\n tags,\n method: method || undefined,\n status: status || undefined,\n level: level || undefined,\n };\n}\n\n/**\n * Create Hono app with dashboard UI and API routes\n */\nexport function createUI(telescope: Telescope): Hono {\n const app = new Hono();\n\n // API routes\n app.get('/api/requests', async (c) => {\n const options = parseQueryOptions(c);\n const requests = await telescope.getRequests(options);\n return c.json(requests);\n });\n\n app.get('/api/requests/:id', async (c) => {\n const request = await telescope.getRequest(c.req.param('id'));\n if (!request) {\n return c.json({ error: 'Request not found' }, 404);\n }\n return c.json(request);\n });\n\n app.get('/api/exceptions', async (c) => {\n const options = parseQueryOptions(c);\n const exceptions = await telescope.getExceptions(options);\n return c.json(exceptions);\n });\n\n app.get('/api/exceptions/:id', async (c) => {\n const exception = await telescope.getException(c.req.param('id'));\n if (!exception) {\n return c.json({ error: 'Exception not found' }, 404);\n }\n return c.json(exception);\n });\n\n app.get('/api/logs', async (c) => {\n const options = parseQueryOptions(c);\n const logs = await telescope.getLogs(options);\n return c.json(logs);\n });\n\n app.get('/api/stats', async (c) => {\n const stats = await telescope.getStats();\n return c.json(stats);\n });\n\n // Static assets\n app.get('/assets/:filename', (c) => {\n const filename = c.req.param('filename');\n const assetPath = `assets/${filename}`;\n const asset = getAsset(assetPath);\n if (asset) {\n return c.body(asset.content, 200, {\n 'Content-Type': asset.contentType,\n 'Cache-Control': 'public, max-age=31536000, immutable',\n });\n }\n return c.notFound();\n });\n\n // Dashboard UI - serve React app\n app.get('/', (c) => {\n const html = getIndexHtml();\n if (!html) {\n return c.text(\n 'Telescope UI not available. Run \"pnpm build:ui\" first.',\n 500,\n );\n }\n return c.html(html);\n });\n\n app.get('/*', (c) => {\n // SPA fallback - serve index.html for client-side routing\n const html = getIndexHtml();\n if (!html) {\n return c.text(\n 'Telescope UI not available. Run \"pnpm build:ui\" first.',\n 500,\n );\n }\n return c.html(html);\n });\n\n return app;\n}\n\n/**\n * Set up WebSocket routes for real-time updates.\n * Requires @hono/node-ws for Node.js or Bun's built-in WebSocket.\n */\nexport function setupWebSocket(\n app: Hono,\n telescope: Telescope,\n upgradeWebSocket: (handler: any) => any,\n): void {\n app.get(\n '/ws',\n upgradeWebSocket(() => ({\n onOpen: (_event: Event, ws: WebSocket) => {\n telescope.addWsClient(ws);\n },\n onClose: (_event: Event, ws: WebSocket) => {\n telescope.removeWsClient(ws);\n },\n onMessage: (event: MessageEvent, ws: WebSocket) => {\n try {\n const data = JSON.parse(event.data);\n if (data.type === 'ping') {\n ws.send(JSON.stringify({ type: 'pong' }));\n }\n } catch {\n // Ignore invalid messages\n }\n },\n })),\n );\n}\n\n/**\n * Get the request ID from Hono context (set by middleware)\n */\nexport function getRequestId(c: Context): string | undefined {\n return c.get(CONTEXT_KEY);\n}\n\n// Re-export types\nexport type { Telescope };\n"],"mappings":"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"}
|
package/dist/server/hono.mjs
CHANGED
|
@@ -27,11 +27,10 @@ function createMiddleware(telescope) {
|
|
|
27
27
|
"PATCH"
|
|
28
28
|
].includes(c.req.method)) try {
|
|
29
29
|
const contentType = c.req.header("content-type") || "";
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
} else if (contentType.includes("text/")) body = await c.req.text();
|
|
30
|
+
const clonedRequest = c.req.raw.clone();
|
|
31
|
+
if (contentType.includes("application/json")) body = await clonedRequest.json();
|
|
32
|
+
else if (contentType.includes("application/x-www-form-urlencoded")) body = Object.fromEntries((await clonedRequest.formData()).entries());
|
|
33
|
+
else if (contentType.includes("text/")) body = await clonedRequest.text();
|
|
35
34
|
} catch {}
|
|
36
35
|
const ip = c.req.header("x-forwarded-for") || c.req.header("x-real-ip");
|
|
37
36
|
try {
|
package/dist/server/hono.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hono.mjs","names":["telescope: Telescope","c: Context","next: Next","headers: Record<string, string>","query: Record<string, string>","body: unknown","responseHeaders: Record<string, string>","responseBody: unknown","app: Hono","upgradeWebSocket: (handler: any) => any","_event: Event","ws: WebSocket","event: MessageEvent"],"sources":["../../src/server/hono.ts"],"sourcesContent":["import { Hono } from 'hono';\nimport type { Context, MiddlewareHandler, Next } from 'hono';\nimport type { Telescope } from '../Telescope';\nimport type { QueryOptions } from '../types';\nimport { getAsset, getIndexHtml } from '../ui-assets';\n\nconst CONTEXT_KEY = 'telescope-request-id';\n\n/**\n * Create Hono middleware that captures requests and responses\n */\nexport function createMiddleware(telescope: Telescope): MiddlewareHandler {\n return async (c: Context, next: Next) => {\n if (!telescope.enabled) {\n return next();\n }\n\n if (telescope.shouldIgnore(c.req.path)) {\n return next();\n }\n\n const startTime = performance.now();\n\n // Capture request data\n const headers: Record<string, string> = {};\n c.req.raw.headers.forEach((value, key) => {\n headers[key] = value;\n });\n\n const url = new URL(c.req.url);\n const query: Record<string, string> = {};\n url.searchParams.forEach((value, key) => {\n query[key] = value;\n });\n\n let body: unknown;\n if (\n telescope.recordBody &&\n ['POST', 'PUT', 'PATCH'].includes(c.req.method)\n ) {\n try {\n const contentType = c.req.header('content-type') || '';\n if (contentType.includes('application/json')) {\n body = await c.req.json();\n } else if (contentType.includes('application/x-www-form-urlencoded')) {\n const formData = await c.req.formData();\n body = Object.fromEntries(formData.entries());\n } else if (contentType.includes('text/')) {\n body = await c.req.text();\n }\n } catch {\n // Ignore body parsing errors\n }\n }\n\n const ip = c.req.header('x-forwarded-for') || c.req.header('x-real-ip');\n\n try {\n await next();\n\n // Capture response data\n const duration = performance.now() - startTime;\n\n const responseHeaders: Record<string, string> = {};\n c.res.headers.forEach((value, key) => {\n responseHeaders[key] = value;\n });\n\n let responseBody: unknown;\n if (telescope.recordBody) {\n try {\n const contentType = c.res.headers.get('content-type') || '';\n if (contentType.includes('application/json')) {\n const cloned = c.res.clone();\n responseBody = await cloned.json();\n }\n } catch {\n // Ignore body parsing errors\n }\n }\n\n const requestId = await telescope.recordRequest({\n method: c.req.method,\n path: c.req.path,\n url: c.req.url,\n headers,\n body,\n query,\n status: c.res.status,\n responseHeaders,\n responseBody,\n duration,\n ip,\n });\n\n c.set(CONTEXT_KEY, requestId);\n } catch (error) {\n await telescope.exception(error as Error);\n throw error;\n }\n };\n}\n\n/**\n * Parse query options from Hono context\n */\nfunction parseQueryOptions(c: Context): QueryOptions {\n const limit = parseInt(c.req.query('limit') || '50', 10);\n const offset = parseInt(c.req.query('offset') || '0', 10);\n const search = c.req.query('search');\n const before = c.req.query('before');\n const after = c.req.query('after');\n const tags = c.req.query('tags')?.split(',').filter(Boolean);\n const method = c.req.query('method');\n const status = c.req.query('status');\n const level = c.req.query('level') as\n | 'debug'\n | 'info'\n | 'warn'\n | 'error'\n | undefined;\n\n return {\n limit: Math.min(limit, 100),\n offset,\n search,\n before: before ? new Date(before) : undefined,\n after: after ? new Date(after) : undefined,\n tags,\n method: method || undefined,\n status: status || undefined,\n level: level || undefined,\n };\n}\n\n/**\n * Create Hono app with dashboard UI and API routes\n */\nexport function createUI(telescope: Telescope): Hono {\n const app = new Hono();\n\n // API routes\n app.get('/api/requests', async (c) => {\n const options = parseQueryOptions(c);\n const requests = await telescope.getRequests(options);\n return c.json(requests);\n });\n\n app.get('/api/requests/:id', async (c) => {\n const request = await telescope.getRequest(c.req.param('id'));\n if (!request) {\n return c.json({ error: 'Request not found' }, 404);\n }\n return c.json(request);\n });\n\n app.get('/api/exceptions', async (c) => {\n const options = parseQueryOptions(c);\n const exceptions = await telescope.getExceptions(options);\n return c.json(exceptions);\n });\n\n app.get('/api/exceptions/:id', async (c) => {\n const exception = await telescope.getException(c.req.param('id'));\n if (!exception) {\n return c.json({ error: 'Exception not found' }, 404);\n }\n return c.json(exception);\n });\n\n app.get('/api/logs', async (c) => {\n const options = parseQueryOptions(c);\n const logs = await telescope.getLogs(options);\n return c.json(logs);\n });\n\n app.get('/api/stats', async (c) => {\n const stats = await telescope.getStats();\n return c.json(stats);\n });\n\n // Static assets\n app.get('/assets/:filename', (c) => {\n const filename = c.req.param('filename');\n const assetPath = `assets/${filename}`;\n const asset = getAsset(assetPath);\n if (asset) {\n return c.body(asset.content, 200, {\n 'Content-Type': asset.contentType,\n 'Cache-Control': 'public, max-age=31536000, immutable',\n });\n }\n return c.notFound();\n });\n\n // Dashboard UI - serve React app\n app.get('/', (c) => {\n const html = getIndexHtml();\n if (!html) {\n return c.text(\n 'Telescope UI not available. Run \"pnpm build:ui\" first.',\n 500,\n );\n }\n return c.html(html);\n });\n\n app.get('/*', (c) => {\n // SPA fallback - serve index.html for client-side routing\n const html = getIndexHtml();\n if (!html) {\n return c.text(\n 'Telescope UI not available. Run \"pnpm build:ui\" first.',\n 500,\n );\n }\n return c.html(html);\n });\n\n return app;\n}\n\n/**\n * Set up WebSocket routes for real-time updates.\n * Requires @hono/node-ws for Node.js or Bun's built-in WebSocket.\n */\nexport function setupWebSocket(\n app: Hono,\n telescope: Telescope,\n upgradeWebSocket: (handler: any) => any,\n): void {\n app.get(\n '/ws',\n upgradeWebSocket(() => ({\n onOpen: (_event: Event, ws: WebSocket) => {\n telescope.addWsClient(ws);\n },\n onClose: (_event: Event, ws: WebSocket) => {\n telescope.removeWsClient(ws);\n },\n onMessage: (event: MessageEvent, ws: WebSocket) => {\n try {\n const data = JSON.parse(event.data);\n if (data.type === 'ping') {\n ws.send(JSON.stringify({ type: 'pong' }));\n }\n } catch {\n // Ignore invalid messages\n }\n },\n })),\n );\n}\n\n/**\n * Get the request ID from Hono context (set by middleware)\n */\nexport function getRequestId(c: Context): string | undefined {\n return c.get(CONTEXT_KEY);\n}\n\n// Re-export types\nexport type { Telescope };\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"hono.mjs","names":["telescope: Telescope","c: Context","next: Next","headers: Record<string, string>","query: Record<string, string>","body: unknown","responseHeaders: Record<string, string>","responseBody: unknown","app: Hono","upgradeWebSocket: (handler: any) => any","_event: Event","ws: WebSocket","event: MessageEvent"],"sources":["../../src/server/hono.ts"],"sourcesContent":["import { Hono } from 'hono';\nimport type { Context, MiddlewareHandler, Next } from 'hono';\nimport type { Telescope } from '../Telescope';\nimport type { QueryOptions } from '../types';\nimport { getAsset, getIndexHtml } from '../ui-assets';\n\nconst CONTEXT_KEY = 'telescope-request-id';\n\n/**\n * Create Hono middleware that captures requests and responses\n */\nexport function createMiddleware(telescope: Telescope): MiddlewareHandler {\n return async (c: Context, next: Next) => {\n if (!telescope.enabled) {\n return next();\n }\n\n if (telescope.shouldIgnore(c.req.path)) {\n return next();\n }\n\n const startTime = performance.now();\n\n // Capture request data\n const headers: Record<string, string> = {};\n c.req.raw.headers.forEach((value, key) => {\n headers[key] = value;\n });\n\n const url = new URL(c.req.url);\n const query: Record<string, string> = {};\n url.searchParams.forEach((value, key) => {\n query[key] = value;\n });\n\n let body: unknown;\n if (\n telescope.recordBody &&\n ['POST', 'PUT', 'PATCH'].includes(c.req.method)\n ) {\n try {\n const contentType = c.req.header('content-type') || '';\n // Clone the request to avoid consuming the body stream\n const clonedRequest = c.req.raw.clone();\n if (contentType.includes('application/json')) {\n body = await clonedRequest.json();\n } else if (contentType.includes('application/x-www-form-urlencoded')) {\n body = Object.fromEntries((await clonedRequest.formData()).entries());\n } else if (contentType.includes('text/')) {\n body = await clonedRequest.text();\n }\n } catch {\n // Ignore body parsing errors\n }\n }\n\n const ip = c.req.header('x-forwarded-for') || c.req.header('x-real-ip');\n\n try {\n await next();\n\n // Capture response data\n const duration = performance.now() - startTime;\n\n const responseHeaders: Record<string, string> = {};\n c.res.headers.forEach((value, key) => {\n responseHeaders[key] = value;\n });\n\n let responseBody: unknown;\n if (telescope.recordBody) {\n try {\n const contentType = c.res.headers.get('content-type') || '';\n if (contentType.includes('application/json')) {\n const cloned = c.res.clone();\n responseBody = await cloned.json();\n }\n } catch {\n // Ignore body parsing errors\n }\n }\n\n const requestId = await telescope.recordRequest({\n method: c.req.method,\n path: c.req.path,\n url: c.req.url,\n headers,\n body,\n query,\n status: c.res.status,\n responseHeaders,\n responseBody,\n duration,\n ip,\n });\n\n c.set(CONTEXT_KEY, requestId);\n } catch (error) {\n await telescope.exception(error as Error);\n throw error;\n }\n };\n}\n\n/**\n * Parse query options from Hono context\n */\nfunction parseQueryOptions(c: Context): QueryOptions {\n const limit = parseInt(c.req.query('limit') || '50', 10);\n const offset = parseInt(c.req.query('offset') || '0', 10);\n const search = c.req.query('search');\n const before = c.req.query('before');\n const after = c.req.query('after');\n const tags = c.req.query('tags')?.split(',').filter(Boolean);\n const method = c.req.query('method');\n const status = c.req.query('status');\n const level = c.req.query('level') as\n | 'debug'\n | 'info'\n | 'warn'\n | 'error'\n | undefined;\n\n return {\n limit: Math.min(limit, 100),\n offset,\n search,\n before: before ? new Date(before) : undefined,\n after: after ? new Date(after) : undefined,\n tags,\n method: method || undefined,\n status: status || undefined,\n level: level || undefined,\n };\n}\n\n/**\n * Create Hono app with dashboard UI and API routes\n */\nexport function createUI(telescope: Telescope): Hono {\n const app = new Hono();\n\n // API routes\n app.get('/api/requests', async (c) => {\n const options = parseQueryOptions(c);\n const requests = await telescope.getRequests(options);\n return c.json(requests);\n });\n\n app.get('/api/requests/:id', async (c) => {\n const request = await telescope.getRequest(c.req.param('id'));\n if (!request) {\n return c.json({ error: 'Request not found' }, 404);\n }\n return c.json(request);\n });\n\n app.get('/api/exceptions', async (c) => {\n const options = parseQueryOptions(c);\n const exceptions = await telescope.getExceptions(options);\n return c.json(exceptions);\n });\n\n app.get('/api/exceptions/:id', async (c) => {\n const exception = await telescope.getException(c.req.param('id'));\n if (!exception) {\n return c.json({ error: 'Exception not found' }, 404);\n }\n return c.json(exception);\n });\n\n app.get('/api/logs', async (c) => {\n const options = parseQueryOptions(c);\n const logs = await telescope.getLogs(options);\n return c.json(logs);\n });\n\n app.get('/api/stats', async (c) => {\n const stats = await telescope.getStats();\n return c.json(stats);\n });\n\n // Static assets\n app.get('/assets/:filename', (c) => {\n const filename = c.req.param('filename');\n const assetPath = `assets/${filename}`;\n const asset = getAsset(assetPath);\n if (asset) {\n return c.body(asset.content, 200, {\n 'Content-Type': asset.contentType,\n 'Cache-Control': 'public, max-age=31536000, immutable',\n });\n }\n return c.notFound();\n });\n\n // Dashboard UI - serve React app\n app.get('/', (c) => {\n const html = getIndexHtml();\n if (!html) {\n return c.text(\n 'Telescope UI not available. Run \"pnpm build:ui\" first.',\n 500,\n );\n }\n return c.html(html);\n });\n\n app.get('/*', (c) => {\n // SPA fallback - serve index.html for client-side routing\n const html = getIndexHtml();\n if (!html) {\n return c.text(\n 'Telescope UI not available. Run \"pnpm build:ui\" first.',\n 500,\n );\n }\n return c.html(html);\n });\n\n return app;\n}\n\n/**\n * Set up WebSocket routes for real-time updates.\n * Requires @hono/node-ws for Node.js or Bun's built-in WebSocket.\n */\nexport function setupWebSocket(\n app: Hono,\n telescope: Telescope,\n upgradeWebSocket: (handler: any) => any,\n): void {\n app.get(\n '/ws',\n upgradeWebSocket(() => ({\n onOpen: (_event: Event, ws: WebSocket) => {\n telescope.addWsClient(ws);\n },\n onClose: (_event: Event, ws: WebSocket) => {\n telescope.removeWsClient(ws);\n },\n onMessage: (event: MessageEvent, ws: WebSocket) => {\n try {\n const data = JSON.parse(event.data);\n if (data.type === 'ping') {\n ws.send(JSON.stringify({ type: 'pong' }));\n }\n } catch {\n // Ignore invalid messages\n }\n },\n })),\n );\n}\n\n/**\n * Get the request ID from Hono context (set by middleware)\n */\nexport function getRequestId(c: Context): string | undefined {\n return c.get(CONTEXT_KEY);\n}\n\n// Re-export types\nexport type { Telescope };\n"],"mappings":";;;;AAMA,MAAM,cAAc;;;;AAKpB,SAAgB,iBAAiBA,WAAyC;AACxE,QAAO,OAAOC,GAAYC,SAAe;AACvC,OAAK,UAAU,QACb,QAAO,MAAM;AAGf,MAAI,UAAU,aAAa,EAAE,IAAI,KAAK,CACpC,QAAO,MAAM;EAGf,MAAM,YAAY,YAAY,KAAK;EAGnC,MAAMC,UAAkC,CAAE;AAC1C,IAAE,IAAI,IAAI,QAAQ,QAAQ,CAAC,OAAO,QAAQ;AACxC,WAAQ,OAAO;EAChB,EAAC;EAEF,MAAM,MAAM,IAAI,IAAI,EAAE,IAAI;EAC1B,MAAMC,QAAgC,CAAE;AACxC,MAAI,aAAa,QAAQ,CAAC,OAAO,QAAQ;AACvC,SAAM,OAAO;EACd,EAAC;EAEF,IAAIC;AACJ,MACE,UAAU,cACV;GAAC;GAAQ;GAAO;EAAQ,EAAC,SAAS,EAAE,IAAI,OAAO,CAE/C,KAAI;GACF,MAAM,cAAc,EAAE,IAAI,OAAO,eAAe,IAAI;GAEpD,MAAM,gBAAgB,EAAE,IAAI,IAAI,OAAO;AACvC,OAAI,YAAY,SAAS,mBAAmB,CAC1C,QAAO,MAAM,cAAc,MAAM;YACxB,YAAY,SAAS,oCAAoC,CAClE,QAAO,OAAO,YAAY,CAAC,MAAM,cAAc,UAAU,EAAE,SAAS,CAAC;YAC5D,YAAY,SAAS,QAAQ,CACtC,QAAO,MAAM,cAAc,MAAM;EAEpC,QAAO,CAEP;EAGH,MAAM,KAAK,EAAE,IAAI,OAAO,kBAAkB,IAAI,EAAE,IAAI,OAAO,YAAY;AAEvE,MAAI;AACF,SAAM,MAAM;GAGZ,MAAM,WAAW,YAAY,KAAK,GAAG;GAErC,MAAMC,kBAA0C,CAAE;AAClD,KAAE,IAAI,QAAQ,QAAQ,CAAC,OAAO,QAAQ;AACpC,oBAAgB,OAAO;GACxB,EAAC;GAEF,IAAIC;AACJ,OAAI,UAAU,WACZ,KAAI;IACF,MAAM,cAAc,EAAE,IAAI,QAAQ,IAAI,eAAe,IAAI;AACzD,QAAI,YAAY,SAAS,mBAAmB,EAAE;KAC5C,MAAM,SAAS,EAAE,IAAI,OAAO;AAC5B,oBAAe,MAAM,OAAO,MAAM;IACnC;GACF,QAAO,CAEP;GAGH,MAAM,YAAY,MAAM,UAAU,cAAc;IAC9C,QAAQ,EAAE,IAAI;IACd,MAAM,EAAE,IAAI;IACZ,KAAK,EAAE,IAAI;IACX;IACA;IACA;IACA,QAAQ,EAAE,IAAI;IACd;IACA;IACA;IACA;GACD,EAAC;AAEF,KAAE,IAAI,aAAa,UAAU;EAC9B,SAAQ,OAAO;AACd,SAAM,UAAU,UAAU,MAAe;AACzC,SAAM;EACP;CACF;AACF;;;;AAKD,SAAS,kBAAkBN,GAA0B;CACnD,MAAM,QAAQ,SAAS,EAAE,IAAI,MAAM,QAAQ,IAAI,MAAM,GAAG;CACxD,MAAM,SAAS,SAAS,EAAE,IAAI,MAAM,SAAS,IAAI,KAAK,GAAG;CACzD,MAAM,SAAS,EAAE,IAAI,MAAM,SAAS;CACpC,MAAM,SAAS,EAAE,IAAI,MAAM,SAAS;CACpC,MAAM,QAAQ,EAAE,IAAI,MAAM,QAAQ;CAClC,MAAM,OAAO,EAAE,IAAI,MAAM,OAAO,EAAE,MAAM,IAAI,CAAC,OAAO,QAAQ;CAC5D,MAAM,SAAS,EAAE,IAAI,MAAM,SAAS;CACpC,MAAM,SAAS,EAAE,IAAI,MAAM,SAAS;CACpC,MAAM,QAAQ,EAAE,IAAI,MAAM,QAAQ;AAOlC,QAAO;EACL,OAAO,KAAK,IAAI,OAAO,IAAI;EAC3B;EACA;EACA,QAAQ,SAAS,IAAI,KAAK;EAC1B,OAAO,QAAQ,IAAI,KAAK;EACxB;EACA,QAAQ;EACR,QAAQ;EACR,OAAO;CACR;AACF;;;;AAKD,SAAgB,SAASD,WAA4B;CACnD,MAAM,MAAM,IAAI;AAGhB,KAAI,IAAI,iBAAiB,OAAO,MAAM;EACpC,MAAM,UAAU,kBAAkB,EAAE;EACpC,MAAM,WAAW,MAAM,UAAU,YAAY,QAAQ;AACrD,SAAO,EAAE,KAAK,SAAS;CACxB,EAAC;AAEF,KAAI,IAAI,qBAAqB,OAAO,MAAM;EACxC,MAAM,UAAU,MAAM,UAAU,WAAW,EAAE,IAAI,MAAM,KAAK,CAAC;AAC7D,OAAK,QACH,QAAO,EAAE,KAAK,EAAE,OAAO,oBAAqB,GAAE,IAAI;AAEpD,SAAO,EAAE,KAAK,QAAQ;CACvB,EAAC;AAEF,KAAI,IAAI,mBAAmB,OAAO,MAAM;EACtC,MAAM,UAAU,kBAAkB,EAAE;EACpC,MAAM,aAAa,MAAM,UAAU,cAAc,QAAQ;AACzD,SAAO,EAAE,KAAK,WAAW;CAC1B,EAAC;AAEF,KAAI,IAAI,uBAAuB,OAAO,MAAM;EAC1C,MAAM,YAAY,MAAM,UAAU,aAAa,EAAE,IAAI,MAAM,KAAK,CAAC;AACjE,OAAK,UACH,QAAO,EAAE,KAAK,EAAE,OAAO,sBAAuB,GAAE,IAAI;AAEtD,SAAO,EAAE,KAAK,UAAU;CACzB,EAAC;AAEF,KAAI,IAAI,aAAa,OAAO,MAAM;EAChC,MAAM,UAAU,kBAAkB,EAAE;EACpC,MAAM,OAAO,MAAM,UAAU,QAAQ,QAAQ;AAC7C,SAAO,EAAE,KAAK,KAAK;CACpB,EAAC;AAEF,KAAI,IAAI,cAAc,OAAO,MAAM;EACjC,MAAM,QAAQ,MAAM,UAAU,UAAU;AACxC,SAAO,EAAE,KAAK,MAAM;CACrB,EAAC;AAGF,KAAI,IAAI,qBAAqB,CAAC,MAAM;EAClC,MAAM,WAAW,EAAE,IAAI,MAAM,WAAW;EACxC,MAAM,aAAa,SAAS,SAAS;EACrC,MAAM,QAAQ,SAAS,UAAU;AACjC,MAAI,MACF,QAAO,EAAE,KAAK,MAAM,SAAS,KAAK;GAChC,gBAAgB,MAAM;GACtB,iBAAiB;EAClB,EAAC;AAEJ,SAAO,EAAE,UAAU;CACpB,EAAC;AAGF,KAAI,IAAI,KAAK,CAAC,MAAM;EAClB,MAAM,OAAO,cAAc;AAC3B,OAAK,KACH,QAAO,EAAE,KACP,4DACA,IACD;AAEH,SAAO,EAAE,KAAK,KAAK;CACpB,EAAC;AAEF,KAAI,IAAI,MAAM,CAAC,MAAM;EAEnB,MAAM,OAAO,cAAc;AAC3B,OAAK,KACH,QAAO,EAAE,KACP,4DACA,IACD;AAEH,SAAO,EAAE,KAAK,KAAK;CACpB,EAAC;AAEF,QAAO;AACR;;;;;AAMD,SAAgB,eACdQ,KACAR,WACAS,kBACM;AACN,KAAI,IACF,OACA,iBAAiB,OAAO;EACtB,QAAQ,CAACC,QAAeC,OAAkB;AACxC,aAAU,YAAY,GAAG;EAC1B;EACD,SAAS,CAACD,QAAeC,OAAkB;AACzC,aAAU,eAAe,GAAG;EAC7B;EACD,WAAW,CAACC,OAAqBD,OAAkB;AACjD,OAAI;IACF,MAAM,OAAO,KAAK,MAAM,MAAM,KAAK;AACnC,QAAI,KAAK,SAAS,OAChB,IAAG,KAAK,KAAK,UAAU,EAAE,MAAM,OAAQ,EAAC,CAAC;GAE5C,QAAO,CAEP;EACF;CACF,GAAE,CACJ;AACF;;;;AAKD,SAAgB,aAAaV,GAAgC;AAC3D,QAAO,EAAE,IAAI,YAAY;AAC1B"}
|
package/package.json
CHANGED
package/src/server/hono.ts
CHANGED
|
@@ -40,13 +40,14 @@ export function createMiddleware(telescope: Telescope): MiddlewareHandler {
|
|
|
40
40
|
) {
|
|
41
41
|
try {
|
|
42
42
|
const contentType = c.req.header('content-type') || '';
|
|
43
|
+
// Clone the request to avoid consuming the body stream
|
|
44
|
+
const clonedRequest = c.req.raw.clone();
|
|
43
45
|
if (contentType.includes('application/json')) {
|
|
44
|
-
body = await
|
|
46
|
+
body = await clonedRequest.json();
|
|
45
47
|
} else if (contentType.includes('application/x-www-form-urlencoded')) {
|
|
46
|
-
|
|
47
|
-
body = Object.fromEntries(formData.entries());
|
|
48
|
+
body = Object.fromEntries((await clonedRequest.formData()).entries());
|
|
48
49
|
} else if (contentType.includes('text/')) {
|
|
49
|
-
body = await
|
|
50
|
+
body = await clonedRequest.text();
|
|
50
51
|
}
|
|
51
52
|
} catch {
|
|
52
53
|
// Ignore body parsing errors
|