Skip to main content
Latest
File
import { UAParser } from 'https://cdn.skypack.dev/ua-parser-js@1.0.33?dts'import { TSchema } from 'https://cdn.skypack.dev/@sinclair/typebox@0.25.23?dts'import { Value } from 'https://cdn.skypack.dev/@sinclair/typebox@0.25.23/value?dts'import type { FetchContext } from './FetchContext.d.ts'import type { Route } from './Route.d.ts'
const errorCodes = { 'Access Denied': 403, 'Bad Request': 400, 'Malformed Request': 405, 'Not Found': 404, 'Payload Too Large': 413, 'Service Unavailable': 503, 'Something Went Wrong': 500, 'Unauthorized': 401}
, errorMessages = { 403: 'Access Denied', 400: 'Bad Request', 405: 'Malformed Request', 404: 'Not Found', 413: 'Payload Too Large', 503: 'Service Unavailable', 500: 'Something Went Wrong', 401: 'Unauthorized'}
export function route< Data = unknown, ParsedBody = unknown, ParsedQuery = unknown, ParsedHeaders = unknown, ParsedCookies = unknown, ParsedParameters = unknown> ( schema: { body?: ParsedBody query?: ParsedQuery headers?: ParsedHeaders cookies?: ParsedCookies parameters?: ParsedParameters data?: Data preValidator?: (c: FetchContext<Data>) => Promise<unknown> | unknown postValidator?: (c: FetchContext<Data, ParsedBody, ParsedQuery, ParsedHeaders, ParsedCookies, ParsedParameters>) => Promise<unknown> | unknown preHandler?: (c: FetchContext<Data, ParsedBody, ParsedQuery, ParsedHeaders, ParsedCookies, ParsedParameters>) => Promise<unknown> | unknown postHandler?: (c: FetchContext<Data, ParsedBody, ParsedQuery, ParsedHeaders, ParsedCookies, ParsedParameters>) => Promise<unknown> | unknown }, handler: ( c: FetchContext<Data, ParsedBody, ParsedQuery, ParsedHeaders, ParsedCookies, ParsedParameters> ) => Promise<unknown> | unknown) { return <Route>{ schema,
async handle( r, env, waitUntil, body, cookies, headers, parameters, query ) { const h = new Headers() let c: number | undefined , p
const context = { env, waitUntil, data: {},
error(error) { const code = typeof error === 'string' ? errorCodes[error] : error
, message = typeof error === 'string' ? error.toLowerCase().split('_').map((word) => word[0].toUpperCase() + word.substring(1) ).join(' ') : errorMessages[error]
if (headers.accept?.includes('application/json')) return { code, message }
c = code
return message },
async mail(options) { // to const to: { name?: string; email: string }[] = []
if (typeof options.to === 'string') to.push({ email: options.to }) else if (options.to instanceof Array) for (const recipient of options.to) to.push(typeof recipient === 'string' ? { email: recipient } : recipient) else to.push(options.to)
// cc const cc: { name?: string; email: string }[] = []
if (typeof options.cc === 'string') cc.push({ email: options.cc }) else if (options.cc instanceof Array) for (const recipient of options.cc) cc.push(typeof recipient === 'string' ? { email: recipient } : recipient) else if (options.cc) cc.push(options.cc)
// bcc const bcc: { name?: string; email: string }[] = []
if (typeof options.bcc === 'string') bcc.push({ email: options.bcc }) else if (options.bcc instanceof Array) for (const recipient of options.bcc) bcc.push(typeof recipient === 'string' ? { email: recipient } : recipient) else if (options.bcc) bcc.push(options.bcc)
const data = { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ personalizations: [{ to, ...(cc.length > 0 && { cc }), ...(bcc.length > 0 && { bcc }), ...(options.dkim?.domain && { dkim_domain: options.to }), ...(options.dkim?.privateKey && { dkim_private_key: options.to }), ...(options.dkim?.selector && { dkim_selector: options.to }) }], from: options.from, subject: options.subject, content: [{ type: options.message.startsWith('<html>') ? 'text/html' : 'text/plain', value: options.message }], ...(options.reply && { reply_to: to[0] }) }) }
if (options.wait === true) await fetch('https://api.mailchannels.net/tx/v1/send', data) else waitUntil(fetch('https://api.mailchannels.net/tx/v1/send', data)) },
req: { raw() { return r.clone() }, body, query, headers, cookies, parameters, geo: { ip: headers['cf-connecting-ip'], city: r.cf.city, region: r.cf.region, country: r.cf.country, continent: r.cf.continent, regionCode: r.cf.regionCode, latitude: r.cf.latitude, longitude: r.cf.longitude, postalCode: r.cf.postalCode, timezone: r.cf.timezone, datacenter: r.cf.colo }, userAgent() { const header = headers['user-agent']
if (!header) return
const userAgent = new UAParser(header)
, browser = userAgent.getBrowser() , cpu = userAgent.getCPU() , device = userAgent.getDevice() , engine = userAgent.getEngine() , os = userAgent.getOS()
return { browser: { name: browser.name, version: browser.version }, cpu: { architecture: cpu.architecture }, device: { model: device.model, type: device.type, vendor: device.vendor }, engine: { name: engine.name, version: engine.version }, os: { name: os.name, version: os.version } } }, buffer: r.arrayBuffer, blob: r.blob, formData: r.formData, stream() { return r.clone().body } },
res: { code(code) { c = code }, cookie(name, value, options) { let cookie = `${name}=${value};`
h.set( 'set-cookie', ( options?.expiresAt && (cookie += ` expires=${options.expiresAt.toUTCString()};`), options?.maxAge && (cookie += ` max-age=${options.maxAge};`), options?.domain && (cookie += ` domain=${options.domain};`), options?.path && (cookie += ` path=${options.path};`), options?.secure && (cookie += ' secure;'), options?.httpOnly && (cookie += ' httpOnly;'), options?.sameSite && (cookie += ` sameSite=${ options.sameSite.charAt(0).toUpperCase() + options.sameSite.slice(1) };`), cookie ) ) }, header(name, value) { h.set(name, value) }, html(payload, code) { h.set('content-type', 'text/html; charset=utf-8;')
p = payload c = code }, json(payload, code) { h.set('content-type', 'application/json; charset=utf-8;')
p = payload c = code }, text(payload, code) { h.set('content-type', 'text/plain; charset=utf-8;')
p = payload c = code }, redirect(destination, code) { h.set('location', destination)
c = code ?? 307 }, blob(payload, code) { p = payload c = code }, stream(payload, code) { p = payload c = code }, buffer(payload, code) { p = payload c = code }, formData(payload, code) { p = payload c = code } } } as FetchContext<Data, ParsedBody, ParsedQuery, ParsedHeaders, ParsedCookies, ParsedParameters>
if (window.__d.cors) h.set('access-control-allow-origin', window.__d.cors)
if (r.method === 'GET') h.set('cache-control', `max-age=${window.__d.cache ?? 0}`)
if (schema.preValidator) { const hookResult = await schema.preValidator(context)
if (hookResult) p = hookResult }
if (!p) { if (schema.parameters && !Value.Check((schema.parameters as unknown) as TSchema, parameters)) throw new Error('Malformed Parameters')
if (schema.query && !Value.Check((schema.query as unknown) as TSchema, query)) throw new Error('Malformed Query')
if (schema.headers && !Value.Check((schema.headers as unknown) as TSchema, headers)) throw new Error('Malformed Headers')
if (schema.body && !Value.Check((schema.body as unknown) as TSchema, body)) throw new Error('Malformed Body')
if (schema.cookies && !Value.Check((schema.cookies as unknown) as TSchema, cookies)) throw new Error('Malformed Cookies') if (schema.postValidator) { const hookResult = await schema.postValidator(context) if (hookResult) p = hookResult } }
if (!p && schema.preHandler) { const hookResult = await schema.preHandler(context)
if (hookResult) p = hookResult }
if (!p) { const handlerResult = await handler(context)
if (handlerResult) p = handlerResult }
if (schema.postHandler) { const hookResult = await schema.postHandler(context)
if (!p && hookResult) p = hookResult }
if (!c) c = 200
if (c !== 200) h.delete('cache-control')
if (h.has('location')) return new Response(null, { headers: h, status: c })
if (!p) return new Response(null, { headers: h, status: c })
if (typeof p === 'string') { // string h.set('content-length', p.length.toString())
if (!h.has('content-type')) h.set('content-type', 'text/plain; charset=utf-8;') } else if (p instanceof Uint8Array || p instanceof ArrayBuffer) { // buffer h.set('content-length', p.byteLength.toString()) } else if (p instanceof Blob) { // blob h.set('content-length', p.size.toString()) } else if (!(p instanceof ReadableStream)) { // object p = JSON.stringify(p)
h.set('content-length', p.length.toString())
if (!h.has('content-type')) h.set('content-type', 'application/json; charset=utf-8;')
if (((p as unknown) as { code: number }).code) c = ((p as unknown) as { code: number }).code }
return new Response(p, { headers: h, status: c }) } }}