@coursebuilder/core
Version:
Core package for Course Builder
1 lines • 15.3 kB
Source Map (JSON)
{"version":3,"sources":["../../../src/lib/utils/web.ts","../../../src/errors.ts","../../../src/lib/utils/actions.ts","../../../src/lib/utils/logger.ts"],"sourcesContent":["import { getToken } from '@auth/core/jwt'\nimport { parse as parseCookie, serialize } from 'cookie'\n\nimport { CourseBuilderError } from '../../errors'\nimport { CourseBuilderConfig } from '../../index'\nimport {\n\tCourseBuilderAction,\n\tRequestInternal,\n\tResponseInternal,\n} from '../../types'\nimport { isCourseBuilderAction } from './actions'\nimport { logger } from './logger'\n\nasync function getBody(\n\treq: Request,\n\tconfig: CourseBuilderConfig,\n): Promise<Record<string, any> | undefined> {\n\tconst headers = Object.fromEntries(req.headers)\n\tconst isStripeWebhook = ['stripe-signature'].every((prop: string) => {\n\t\treturn prop in headers\n\t})\n\n\tif (isStripeWebhook) {\n\t\tlet parsedBody\n\t\tconst stripeProvider = config.providers.find((p: any) => p.id === 'stripe')\n\t\tparsedBody = await req.text()\n\t\tstripeProvider?.options?.paymentsAdapter.verifyWebhookSignature(\n\t\t\tparsedBody,\n\t\t\theaders['stripe-signature'],\n\t\t)\n\t\tparsedBody = JSON.parse(parsedBody)\n\t\treturn parsedBody\n\t}\n\n\tif (!('body' in req) || !req.body || req.method !== 'POST') return\n\n\tconst contentType = req.headers.get('content-type')\n\tif (contentType?.includes('application/json')) {\n\t\ttry {\n\t\t\treturn await req.json()\n\t\t} catch (e) {\n\t\t\tlogger.error(e as Error)\n\t\t\treturn undefined\n\t\t}\n\t} else if (contentType?.includes('application/x-www-form-urlencoded')) {\n\t\tconst params = new URLSearchParams(await req.text())\n\t\treturn Object.fromEntries(params)\n\t}\n}\n\n/**\n * takes a `Request` object and converts it into an internal request object\n * that we use throughout the system\n * @param req\n * @param config\n * @returns\n */\nexport async function toInternalRequest(\n\treq: Request,\n\tconfig: CourseBuilderConfig,\n): Promise<RequestInternal | undefined> {\n\ttry {\n\t\tconfig.basePath ??= '/coursebuilder'\n\n\t\tconst url = new URL(req.url)\n\n\t\tconst { action, providerId } = parseActionAndProviderId(\n\t\t\turl.pathname,\n\t\t\tconfig.basePath,\n\t\t)\n\n\t\tconsole.debug({\n\t\t\turl,\n\t\t\taction,\n\t\t\tproviderId,\n\t\t})\n\n\t\treturn {\n\t\t\turl,\n\t\t\taction,\n\t\t\tproviderId,\n\t\t\tmethod: req.method as 'POST' | 'GET',\n\t\t\theaders: Object.fromEntries(req.headers),\n\t\t\tbody: req.body ? await getBody(req, config) : undefined,\n\t\t\tcookies: parseCookie(req.headers.get('cookie') ?? '') ?? {},\n\t\t\terror: url.searchParams.get('error') ?? undefined,\n\t\t\tquery: Object.fromEntries(url.searchParams),\n\t\t}\n\t} catch (e) {\n\t\tlogger.error(e as Error)\n\t\tlogger.debug('request', req)\n\t}\n}\n\nexport function parseActionAndProviderId(\n\tpathname: string,\n\tbase: string,\n): {\n\taction: CourseBuilderAction\n\tproviderId?: string\n} {\n\tconst a = pathname.match(new RegExp(`^${base}(.+)`))\n\n\tif (a === null) throw new UnknownAction(`Cannot parse action at ${pathname}`)\n\n\tconst [_, actionAndProviderId] = a\n\n\tconst b = actionAndProviderId.replace(/^\\//, '').split('/')\n\n\tif (b.length !== 1 && b.length !== 2)\n\t\tthrow new UnknownAction(`**Cannot parse action at ${pathname}`)\n\n\tconst [action, providerId] = b\n\n\tif (!isCourseBuilderAction(action))\n\t\tthrow new UnknownAction(`***Cannot parse action at ${pathname}`)\n\n\tif (\n\t\tproviderId &&\n\t\t![\n\t\t\t'webhook',\n\t\t\t'srt',\n\t\t\t'session',\n\t\t\t'subscribe-to-list',\n\t\t\t'checkout',\n\t\t\t'redeem',\n\t\t\t'subscriber',\n\t\t\t'lookup',\n\t\t\t'claimed',\n\t\t\t'nameUpdate',\n\t\t\t'transfer',\n\t\t\t'refund',\n\t\t\t'create-magic-link',\n\t\t].includes(action)\n\t)\n\t\tthrow new UnknownAction(\n\t\t\t`**** Cannot parse action at ${pathname}. add it to the list in web.ts?`,\n\t\t)\n\n\treturn { action, providerId }\n}\n\nexport class UnknownAction extends CourseBuilderError {\n\tstatic type = 'UnknownAction'\n}\n\nexport function toResponse(res: ResponseInternal): Response {\n\tconst headers = new Headers(res.headers)\n\n\tres.cookies?.forEach((cookie) => {\n\t\tconst { name, value, options } = cookie\n\t\tconst cookieHeader = serialize(name, value, options)\n\t\tif (headers.has('Set-Cookie')) headers.append('Set-Cookie', cookieHeader)\n\t\telse headers.set('Set-Cookie', cookieHeader)\n\t})\n\n\tlet body = res.body\n\n\tif (headers.get('content-type') === 'application/json')\n\t\tbody = JSON.stringify(res.body)\n\telse if (headers.get('content-type') === 'application/x-www-form-urlencoded')\n\t\tbody = new URLSearchParams(res.body).toString()\n\n\tconst status = res.redirect ? 302 : (res.status ?? 200)\n\tconst response = new Response(body, { headers, status })\n\n\tif (res.redirect) response.headers.set('Location', res.redirect)\n\n\treturn response\n}\n","type ErrorOptions = Error | Record<string, unknown>\n\ntype ErrorType =\n\t| 'AdapterError'\n\t| 'UnknownAction'\n\t| 'UnsupportedStrategy'\n\t| 'MissingAdapter'\n\t| 'MissingAdapterMethods'\n\nexport class CourseBuilderError extends Error {\n\t/** The error type. Used to identify the error in the logs. */\n\ttype: ErrorType\n\t/**\n\t * Determines on which page an error should be handled. Typically `signIn` errors can be handled in-page.\n\t * Default is `\"error\"`.\n\t * @internal\n\t */\n\tkind?: 'error'\n\tcause?: Record<string, unknown> & { err?: Error }\n\tconstructor(\n\t\tmessage?: string | Error | ErrorOptions,\n\t\terrorOptions?: ErrorOptions,\n\t) {\n\t\tif (message instanceof Error) {\n\t\t\tsuper(undefined, {\n\t\t\t\tcause: { err: message, ...(message.cause as any), ...errorOptions },\n\t\t\t})\n\t\t} else if (typeof message === 'string') {\n\t\t\tif (errorOptions instanceof Error) {\n\t\t\t\terrorOptions = { err: errorOptions, ...(errorOptions.cause as any) }\n\t\t\t}\n\t\t\tsuper(message, errorOptions)\n\t\t} else {\n\t\t\tsuper(undefined, message)\n\t\t}\n\t\tthis.name = this.constructor.name\n\t\t// @ts-expect-error https://github.com/microsoft/TypeScript/issues/3841\n\t\tthis.type = this.constructor.type ?? 'CourseBuilderError'\n\t\t// @ts-expect-error https://github.com/microsoft/TypeScript/issues/3841\n\t\tthis.kind = this.constructor.kind ?? 'error'\n\n\t\tError.captureStackTrace?.(this, this.constructor)\n\t\tconst url = `https://errors.authjs.dev#${this.type.toLowerCase()}`\n\t\tthis.message += `${this.message ? '. ' : ''}Read more at ${url}`\n\t}\n}\n\nexport class AdapterError extends CourseBuilderError {\n\tstatic type = 'AdapterError'\n}\n\nexport class UnsupportedStrategy extends CourseBuilderError {\n\tstatic type = 'UnsupportedStrategy'\n}\n\nexport class MissingAdapter extends CourseBuilderError {\n\tstatic type = 'MissingAdapter'\n}\n\nexport class MissingAdapterMethods extends CourseBuilderError {\n\tstatic type = 'MissingAdapterMethods'\n}\n","import { CourseBuilderAction } from '../../types'\n\nconst actions: CourseBuilderAction[] = [\n\t'webhook',\n\t'srt',\n\t'session',\n\t'subscribe-to-list',\n\t'checkout',\n\t'redeem',\n\t'prices-formatted',\n\t'subscriber',\n\t'purchases',\n\t'lookup',\n\t'claimed',\n\t'nameUpdate',\n\t'transfer',\n\t'refund',\n\t'create-magic-link',\n]\n\nexport function isCourseBuilderAction(\n\taction: string,\n): action is CourseBuilderAction {\n\treturn actions.includes(action as CourseBuilderAction)\n}\n","import { CourseBuilderError } from '../../errors.js'\n\nexport type WarningCode =\n\t| 'debug-enabled'\n\t| 'env-url-basepath-redundant'\n\t| 'env-url-basepath-mismatch'\n\nexport interface LoggerInstance extends Record<string, Function> {\n\twarn: (code: WarningCode) => void\n\terror: (error: Error) => void\n\tdebug: (message: string, metadata?: unknown) => void\n}\n\nconst red = '\\x1b[31m'\nconst yellow = '\\x1b[33m'\nconst grey = '\\x1b[38;5;246m'\nconst reset = '\\x1b[0m'\n\nexport const logger: LoggerInstance = {\n\terror(error) {\n\t\tconst name = error instanceof CourseBuilderError ? error.type : error.name\n\t\tconsole.error(\n\t\t\t`${red}[coursebuilder][error]${reset} ${name}: ${error.message}`,\n\t\t)\n\t\tif (\n\t\t\terror.cause &&\n\t\t\ttypeof error.cause === 'object' &&\n\t\t\t'err' in error.cause &&\n\t\t\terror.cause.err instanceof Error\n\t\t) {\n\t\t\tconst { err, ...data } = error.cause\n\t\t\tconsole.error(`${red}[coursebuilder][cause]${reset}:`, err.stack)\n\t\t\tif (data)\n\t\t\t\tconsole.error(\n\t\t\t\t\t`${red}[coursebuilder][details]${reset}:`,\n\t\t\t\t\tJSON.stringify(data, null, 2),\n\t\t\t\t)\n\t\t} else if (error.stack) {\n\t\t\tconsole.error(error.stack.replace(/.*/, '').substring(1))\n\t\t}\n\t},\n\twarn(code) {\n\t\tconst url = `https://warnings.coursebuilder.dev#${code}`\n\t\tconsole.warn(\n\t\t\t`${yellow}[coursebuilder][warn][${code}]${reset}`,\n\t\t\t`Read more: ${url}`,\n\t\t)\n\t},\n\tdebug(message, metadata) {\n\t\tconsole.log(\n\t\t\t`${grey}[coursebuilder][debug]:${reset} ${message}`,\n\t\t\tJSON.stringify(metadata, null, 2),\n\t\t)\n\t},\n}\n\n/**\n * Override the built-in logger with user's implementation.\n * Any `undefined` level will use the default logger.\n */\nexport function setLogger(\n\tnewLogger: Partial<LoggerInstance> = {},\n\tdebug?: boolean,\n) {\n\t// Turn off debug logging if `debug` isn't set to `true`\n\tif (!debug) logger.debug = () => {}\n\n\tif (newLogger.error) logger.error = newLogger.error\n\tif (newLogger.warn) logger.warn = newLogger.warn\n\tif (newLogger.debug) logger.debug = newLogger.debug\n}\n"],"mappings":"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","names":["CourseBuilderError","Error","message","errorOptions","type","kind","cause","undefined","err","name","captureStackTrace","url","toLowerCase","actions","isCourseBuilderAction","action","includes","red","yellow","grey","reset","logger","error","name","CourseBuilderError","type","console","message","cause","err","Error","data","stack","JSON","stringify","replace","substring","warn","code","url","debug","metadata","log","getBody","req","config","headers","Object","fromEntries","isStripeWebhook","every","prop","parsedBody","stripeProvider","providers","find","p","id","text","options","paymentsAdapter","verifyWebhookSignature","JSON","parse","body","method","contentType","get","includes","json","e","logger","error","undefined","params","URLSearchParams","toInternalRequest","basePath","url","URL","action","providerId","parseActionAndProviderId","pathname","console","debug","cookies","parseCookie","searchParams","query","base","a","match","RegExp","UnknownAction","_","actionAndProviderId","b","replace","split","length","isCourseBuilderAction","CourseBuilderError","type","toResponse","res","Headers","forEach","cookie","name","value","cookieHeader","serialize","has","append","set","stringify","toString","status","redirect","response","Response"]}