File
Blob: src/worker/http.ts
| 1 | export class HttpError extends Error { |
| 2 | readonly status: number; |
| 3 | readonly code: string; |
| 4 | readonly detail?: Record<string, unknown>; |
| 5 | |
| 6 | constructor(status: number, code: string, message: string, detail?: Record<string, unknown>) { |
| 7 | super(message); |
| 8 | this.name = "HttpError"; |
| 9 | this.status = status; |
| 10 | this.code = code; |
| 11 | this.detail = detail; |
| 12 | } |
| 13 | |
| 14 | toResponse(): Response { |
| 15 | return new Response(JSON.stringify({ error: this.code, message: this.message, ...(this.detail ?? {}) }), { |
| 16 | status: this.status, |
| 17 | headers: { "content-type": "application/json" }, |
| 18 | }); |
| 19 | } |
| 20 | } |
| 21 | |
| 22 | // JSON response carrying a one-time secret (invite URL, OAuth client |
| 23 | // secret, rotated client secret). POST responses are not cached by |
| 24 | // default, but explicit `no-store` removes the chance that a browser |
| 25 | // extension, dev tool, intermediary, or edge cache retains the value |
| 26 | // after the user closes the tab. `private` is defense-in-depth for |
| 27 | // caches that ignore `no-store` but honor `private`. Pragma is |
| 28 | // duplicated for HTTP/1.0 and strict legacy proxies. |
| 29 | export const secretJsonResponse = (payload: unknown, status = 200): Response => |
| 30 | new Response(JSON.stringify(payload), { |
| 31 | status, |
| 32 | headers: { |
| 33 | "content-type": "application/json", |
| 34 | "cache-control": "no-store, private, max-age=0", |
| 35 | pragma: "no-cache", |
| 36 | }, |
| 37 | }); |