Fix switchable runtimes

This commit is contained in:
Catalin Pinte 2022-10-19 09:34:59 +03:00
parent dae40fd7f1
commit 8873d6fa5d
48 changed files with 335 additions and 257 deletions

View File

@ -53,7 +53,9 @@
"cookie": "^0.4.1",
"immutability-helper": "^3.1.1",
"js-cookie": "^3.0.1",
"lodash.debounce": "^4.0.8"
"jsonwebtoken": "^8.5.1",
"lodash.debounce": "^4.0.8",
"uuidv4": "^6.2.13"
},
"peerDependencies": {
"next": "^12",
@ -65,6 +67,7 @@
"@taskr/esnext": "^1.1.0",
"@taskr/watch": "^1.1.0",
"@types/cookie": "^0.4.1",
"@types/jsonwebtoken": "^8.5.7",
"@types/lodash.debounce": "^4.0.6",
"@types/node": "^17.0.8",
"@types/node-fetch": "^2.6.2",

View File

@ -1,8 +1,5 @@
import type { CheckoutEndpoint } from '.'
import getCustomerId from '../../utils/get-customer-id'
import jwt from '@tsndr/cloudflare-worker-jwt'
import { uuid } from '@cfworker/uuid'
import { NextResponse } from 'next/server'
const fullCheckout = true
@ -24,6 +21,7 @@ const getCheckout: CheckoutEndpoint['handlers']['getCheckout'] = async ({
method: 'POST',
}
)
const customerId =
customerToken && (await getCustomerId({ customerToken, config }))
@ -33,6 +31,17 @@ const getCheckout: CheckoutEndpoint['handlers']['getCheckout'] = async ({
return { redirectTo: data.checkout_url }
}
} else {
// Dynamically import uuid & jsonwebtoken based on the runtime
const { uuid } =
process.env.NEXT_RUNTIME === 'edge'
? await import('@cfworker/uuid')
: await import('uuidv4')
const jwt =
process.env.NEXT_RUNTIME === 'edge'
? await import('@tsndr/cloudflare-worker-jwt')
: await import('jsonwebtoken')
const dateCreated = Math.round(new Date().getTime() / 1000)
const payload = {
iss: config.storeApiClientId,
@ -80,7 +89,7 @@ const getCheckout: CheckoutEndpoint['handlers']['getCheckout'] = async ({
</html>
`
return new NextResponse(html, {
return new Response(html, {
headers: {
'Content-Type': 'text/html',
},

View File

@ -1,6 +1,5 @@
import type { LoginEndpoint } from '.'
import { NextResponse } from 'next/server'
import { FetcherError } from '@vercel/commerce/utils/errors'
import { CommerceAPIError } from '@vercel/commerce/api/utils/errors'
@ -12,7 +11,7 @@ const login: LoginEndpoint['handlers']['login'] = async ({
commerce,
}) => {
try {
const res = new NextResponse(null)
const res = new Response()
await commerce.login({ variables: { email, password }, config, res })
return {
status: res.status,

View File

@ -1,6 +1,4 @@
import type { SignupEndpoint } from '.'
import { NextResponse } from 'next/server'
import { CommerceAPIError } from '@vercel/commerce/api/utils/errors'
import { BigcommerceApiError } from '../../utils/errors'
@ -39,7 +37,7 @@ const signup: SignupEndpoint['handlers']['signup'] = async ({
}
}
const res = new NextResponse()
const res = new Response()
// Login the customer right after creating it
await commerce.login({ variables: { email, password }, res, config })

View File

@ -7,7 +7,6 @@ import type { LoginMutation } from '../../../schema'
import type { RecursivePartial } from '../utils/types'
import concatHeader from '../utils/concat-cookie'
import type { BigcommerceConfig, Provider } from '..'
import type { NextResponse } from 'next/server'
export const loginMutation = /* GraphQL */ `
mutation login($email: String!, $password: String!) {
@ -23,14 +22,14 @@ export default function loginOperation({
async function login<T extends LoginOperation>(opts: {
variables: T['variables']
config?: BigcommerceConfig
res: NextResponse
res: Response
}): Promise<T['data']>
async function login<T extends LoginOperation>(
opts: {
variables: T['variables']
config?: BigcommerceConfig
res: NextResponse
res: Response
} & OperationOptions
): Promise<T['data']>
@ -42,7 +41,7 @@ export default function loginOperation({
}: {
query?: string
variables: T['variables']
res: NextResponse
res: Response
config?: BigcommerceConfig
}): Promise<T['data']> {
config = commerce.getConfig(config)

View File

@ -1,11 +1,9 @@
import type { GetAPISchema } from '..'
import type { CartSchema } from '../../types/cart'
import parse from '../utils/parse-output'
import { parse, getInput } from '../utils'
import validateHandlers from '../utils/validate-handlers'
import { getInput } from '../utils'
import {
getCartBodySchema,
addItemBodySchema,

View File

@ -6,7 +6,7 @@ import {
searchProductBodySchema,
searchProductsSchema,
} from '../../../schemas/product'
import parse from '../../utils/parse-output'
import { parse } from '../../utils'
const productsEndpoint: GetAPISchema<
any,
@ -27,8 +27,7 @@ const productsEndpoint: GetAPISchema<
const res = await handlers['getProducts']({ ...ctx, body })
res.headers = {
'Cache-Control':
'max-age=0, s-maxage=3600, stale-while-revalidate=60, public',
'Cache-Control': 'max-age=0, s-maxage=3600, stale-while-revalidate, public',
...res.headers,
}

View File

@ -7,10 +7,8 @@ import {
submitCheckoutBodySchema,
} from '../../schemas/checkout'
import { parse, getInput } from '../utils'
import validateHandlers from '../utils/validate-handlers'
import parse from '../utils/parse-output'
import { z } from 'zod'
import { getInput } from '../utils'
const checkoutEndpoint: GetAPISchema<
any,
@ -31,7 +29,7 @@ const checkoutEndpoint: GetAPISchema<
if (req.method === 'GET') {
const body = getCheckoutBodySchema.parse({ ...input, cartId })
const res = await handlers['getCheckout']({ ...ctx, body })
return parse(res, checkoutSchema.optional().or(z.string()))
return parse(res, checkoutSchema.optional())
}
// Create checkout

View File

@ -1,7 +1,6 @@
import type { CustomerAddressSchema } from '../../../types/customer/address'
import type { GetAPISchema } from '../..'
import parse from '../../utils/parse-output'
import validateHandlers from '../../utils/validate-handlers'
import {
@ -11,7 +10,7 @@ import {
updateAddressBodySchema,
} from '../../../schemas/customer'
import { getInput } from '../../utils'
import { parse, getInput } from '../../utils'
import { getCartBodySchema } from '../../../schemas/cart'
// create a function that returns a function

View File

@ -3,16 +3,15 @@ import type { GetAPISchema } from '../..'
import { z } from 'zod'
import parse from '../../utils/parse-output'
import validateHandlers from '../../utils/validate-handlers'
import {
cardSchema,
addCardBodySchema,
deleteCardBodySchema,
updateCardBodySchema,
} from '../../../schemas/customer'
import { getInput } from '../../utils'
import { parse, getInput } from '../../utils'
import validateHandlers from '../../utils/validate-handlers'
const customerCardEndpoint: GetAPISchema<
any,

View File

@ -1,7 +1,7 @@
import type { CustomerSchema } from '../../../types/customer'
import type { GetAPISchema } from '../..'
import parse from '../../utils/parse-output'
import { parse } from '../../utils'
import validateHandlers from '../../utils/validate-handlers'
import { customerSchema } from '../../../schemas/customer'

View File

@ -1,7 +1,5 @@
import type { APIProvider, CommerceAPI, EndpointHandler } from '..'
import { NextRequest, NextResponse } from 'next/server'
import { normalizeApiError } from '../utils/errors'
import edgeHandler from '../utils/edge-handler'
import nodeHandler from '../utils/node-handler'
/**
* Next.js Commerce API endpoints handler. Based on the path, it will call the corresponding endpoint handler,
@ -9,72 +7,4 @@ import { normalizeApiError } from '../utils/errors'
* @param {CommerceAPI} commerce The Commerce API instance.
* @param endpoints An object containing the handlers for each endpoint.
*/
export default function createEndpoints<P extends APIProvider>(
commerce: CommerceAPI<P>,
endpoints: Record<string, (commerce: CommerceAPI<P>) => EndpointHandler>
) {
const endpointsKeys = Object.keys(endpoints)
const handlers = endpointsKeys.reduce<Record<string, EndpointHandler>>(
(acc, endpoint) =>
Object.assign(acc, {
[endpoint]: endpoints[endpoint](commerce),
}),
{}
)
return async (req: NextRequest) => {
try {
const { pathname } = new URL(req.url)
/**
* Get the current endpoint by removing the leading and trailing slash & base path.
* Csovers: /api/commerce/cart & /checkout
*/
const endpoint = pathname
.replace('/api/commerce/', '')
.replace(/^\/|\/$/g, '')
// Check if the handler for this path exists and return a 404 if it doesn't
if (!endpointsKeys.includes(endpoint)) {
throw new Error(
`Endpoint "${endpoint}" not implemented. Please use one of the available api endpoints: ${endpointsKeys.join(
', '
)}`
)
}
/**
* Executes the handler for this endpoint, provided by the provider,
* parses the input body and returns the parsed output
*/
const output = await handlers[endpoint](req)
// If the output is a NextResponse, return it directly (E.g. checkout page & validateMethod util)
if (output instanceof NextResponse) {
return output
}
// If the output contains a redirectTo property, return a NextResponse with the redirect
if (output.redirectTo) {
return NextResponse.redirect(output.redirectTo, {
headers: output.headers,
})
}
const { data = null, errors, status, headers } = output
return NextResponse.json(
{ data, errors },
{
status,
headers,
}
)
} catch (error) {
const output = normalizeApiError(error)
return output instanceof NextResponse
? output
: NextResponse.json(output, { status: output.status ?? 500 })
}
}
}
export default process.env.NEXT_RUNTIME === 'edge' ? edgeHandler : nodeHandler

View File

@ -1,9 +1,9 @@
import type { GetAPISchema } from '..'
import type { LoginSchema } from '../../types/login'
import { getInput } from '../utils'
import validateHandlers from '../utils/validate-handlers'
import { getInput } from '../utils'
import { loginBodySchema } from '../../schemas/auth'
const loginEndpoint: GetAPISchema<
@ -16,9 +16,10 @@ const loginEndpoint: GetAPISchema<
POST: handlers['login'],
GET: handlers['login'],
})
const input = await getInput(req)
const body = loginBodySchema.parse(input)
return await handlers['login']({ ...ctx, body })
return handlers['login']({ ...ctx, body })
}
export default loginEndpoint

View File

@ -3,7 +3,6 @@ import type { LogoutSchema } from '../../types/logout'
import { logoutBodySchema } from '../../schemas/auth'
import validateHandlers from '../utils/validate-handlers'
import { normalizeApiError } from '../utils/errors'
const logoutEndpoint: GetAPISchema<
any,
@ -21,7 +20,7 @@ const logoutEndpoint: GetAPISchema<
typeof redirectTo === 'string' ? { redirectTo } : {}
)
return await handlers['logout']({ ...ctx, body })
return handlers['logout']({ ...ctx, body })
}
export default logoutEndpoint

View File

@ -1,9 +1,9 @@
import type { GetAPISchema } from '..'
import type { SignupSchema } from '../../types/signup'
import { getInput } from '../utils'
import validateHandlers from '../utils/validate-handlers'
import { getInput } from '../utils'
import { signupBodySchema } from '../../schemas/auth'
const signupEndpoint: GetAPISchema<
@ -21,7 +21,7 @@ const signupEndpoint: GetAPISchema<
const cartId = cookies.get(config.cartCookie)
const body = signupBodySchema.parse({ ...input, cartId })
return await handlers['signup']({ ...ctx, body })
return handlers['signup']({ ...ctx, body })
}
export default signupEndpoint

View File

@ -1,9 +1,8 @@
import type { GetAPISchema } from '..'
import type { WishlistSchema } from '../../types/wishlist'
import validateHandlers from '../utils/validate-handlers'
import { parse, getInput } from '../utils'
import { getInput } from '../utils'
import {
wishlistSchema,
addItemBodySchema,
@ -11,7 +10,7 @@ import {
getWishlistBodySchema,
} from '../../schemas/whishlist'
import parse from '../utils/parse-output'
import validateHandlers from '../utils/validate-handlers'
const wishlistEndpoint: GetAPISchema<
any,
@ -53,7 +52,7 @@ const wishlistEndpoint: GetAPISchema<
output = await handlers['removeItem']({ ...ctx, body })
}
return output ? parse(output, wishlistSchema.optional()) : { status: 40 }
return output ? parse(output, wishlistSchema.optional()) : { status: 405 }
}
export default wishlistEndpoint

View File

@ -1,5 +1,4 @@
import type { NextRequest } from 'next/server'
import type { APIEndpoint, APIHandler, APIResponse } from './utils/types'
import type { CartSchema } from '../types/cart'
import type { CustomerSchema } from '../types/customer'
@ -12,7 +11,6 @@ import type { CheckoutSchema } from '../types/checkout'
import type { CustomerCardSchema } from '../types/customer/card'
import type { CustomerAddressSchema } from '../types/customer/address'
import { geolocation } from '@vercel/edge'
import { withOperationCallback } from './utils/with-operation-callback'
import {
@ -140,7 +138,6 @@ export function getEndpoint<
config: cfg,
handlers: context.handlers,
options: context.options ?? {},
geolocation: geolocation(req),
})
}
}

View File

@ -8,7 +8,6 @@ import type {
GetProductOperation,
} from '../types/product'
import type { APIProvider, CommerceAPI } from '.'
import { NextResponse } from 'next/server'
const noop = () => {
throw new Error('Not implemented')
@ -44,14 +43,14 @@ export type Operations<P extends APIProvider> = {
<T extends LoginOperation>(opts: {
variables: T['variables']
config?: P['config']
res: NextResponse
res: Response
}): Promise<T['data']>
<T extends LoginOperation>(
opts: {
variables: T['variables']
config?: P['config']
res: NextResponse
res: Response
} & OperationOptions
): Promise<T['data']>
}

View File

@ -0,0 +1,82 @@
import type { APIProvider, CommerceAPI, EndpointHandler } from '..'
import type { NextRequest } from 'next/server'
import { normalizeApiError } from './errors'
export default function edgeApi<P extends APIProvider>(
commerce: CommerceAPI<P>,
endpoints: Record<string, (commerce: CommerceAPI<P>) => EndpointHandler>
) {
const endpointsKeys = Object.keys(endpoints)
const handlers = endpointsKeys.reduce<Record<string, EndpointHandler>>(
(acc, endpoint) =>
Object.assign(acc, {
[endpoint]: endpoints[endpoint](commerce),
}),
{}
)
return async (req: NextRequest) => {
try {
const { pathname } = new URL(req.url)
/**
* Get the current endpoint by removing the leading and trailing slash & base path.
* Csovers: /api/commerce/cart & /checkout
*/
const endpoint = pathname
.replace('/api/commerce/', '')
.replace(/^\/|\/$/g, '')
// Check if the handler for this path exists and return a 404 if it doesn't
if (!endpointsKeys.includes(endpoint)) {
throw new Error(
`Endpoint "${endpoint}" not implemented. Please use one of the available api endpoints: ${endpointsKeys.join(
', '
)}`
)
}
/**
* Executes the handler for this endpoint, provided by the provider,
* parses the input body and returns the parsed output
*/
const output = await handlers[endpoint](req)
// If the output is a Response, return it directly (E.g. checkout page & validateMethod util)
if (output instanceof Response) {
return output
}
const { headers } = output
// If the output contains a redirectTo property, return a Response with the redirect
if (output.redirectTo) {
return new Response(null, {
status: 302,
headers: {
...headers,
Location: output.redirectTo,
},
})
}
// Otherwise, return a JSON response with the output data or errors returned by the handler
const { data = null, errors, status } = output
return new Response(JSON.stringify({ data, errors }), {
status,
headers,
})
} catch (error) {
const output = normalizeApiError(error)
if (output instanceof Response) {
return output
}
const { status = 500, ...rest } = output
return output instanceof Response
? output
: new Response(JSON.stringify(rest), { status })
}
}
}

View File

@ -1,13 +1,14 @@
import { NextRequest, NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
import { CommerceError } from '../../utils/errors'
import { ZodError } from 'zod'
export class CommerceAPIResponseError extends Error {
status: number
res: NextResponse
res: Response
data: any
constructor(msg: string, res: NextResponse, data?: any) {
constructor(msg: string, res: Response, data?: any) {
super(msg)
this.name = 'CommerceApiError'
this.status = res.status

View File

@ -1,3 +1,52 @@
import type { NextApiRequest } from 'next'
import type { ZodSchema } from 'zod'
import type { APIResponse } from './types'
import { NextRequest } from 'next/server'
/**
* Parses the output data of the API handler and returns a valid APIResponse
* or throws an error if the data is invalid.
* @param res APIResponse
* @param parser ZodSchema
*/
export const parse = <T>(res: APIResponse<T>, parser: ZodSchema) => {
if (res.data) {
res.data = parser.parse(res.data)
}
return res
}
/**
* Returns the body of the request as a JSON object.
* @param req NextRequest
*/
export const getInput = (req: NextRequest) => req.json().catch(() => ({}))
/**
* Convert NextApiRequest to NextRequest
* @param req NextApiRequest
* @param path string
*/
export const transformRequest = (req: NextApiRequest, path: string) => {
let body
const headers = new Headers()
for (let i = 0; i < req.rawHeaders.length; i += 2) {
headers.append(req.rawHeaders[i], req.rawHeaders[i + 1])
}
if (
req.method === 'POST' ||
req.method === 'PUT' ||
req.method === 'DELETE'
) {
body = JSON.stringify(req.body)
}
return new NextRequest(`https://${req.headers.host}/api/commerce/${path}`, {
headers,
method: req.method,
body,
})
}

View File

@ -0,0 +1,81 @@
import type { NextApiRequest, NextApiResponse } from 'next'
import type { APIProvider, CommerceAPI, EndpointHandler } from '..'
import { normalizeApiError } from './errors'
import { transformRequest } from '.'
export default function nodeApi<P extends APIProvider>(
commerce: CommerceAPI<P>,
endpoints: {
[key: string]: (commerce: CommerceAPI<P>) => EndpointHandler
}
) {
const paths = Object.keys(endpoints)
const handlers = paths.reduce<Record<string, EndpointHandler>>(
(acc, path) =>
Object.assign(acc, {
[path]: endpoints[path](commerce),
}),
{}
)
return async (req: NextApiRequest, res: NextApiResponse) => {
try {
if (!req.query.commerce) {
throw new Error(
'Invalid configuration. Please make sure that the /pages/api/commerce/[[...commerce]].ts route is configured correctly, and it passes the commerce instance.'
)
}
/**
* Get the url path
*/
const path = Array.isArray(req.query.commerce)
? req.query.commerce.join('/')
: req.query.commerce
// Check if the handler for this path exists and return a 404 if it doesn't
if (!paths.includes(path)) {
throw new Error(
`Endpoint handler not implemented. Please use one of the available api endpoints: ${paths.join(
', '
)}`
)
}
const newReq = transformRequest(req, path)
const output = await handlers[path](newReq)
if (output instanceof Response) {
return res.end(output.body)
}
const { status, errors, data, redirectTo, headers } = output
if (headers) {
for (const [key, value] of Object.entries(headers)) {
res.setHeader(key, value)
}
}
if (redirectTo) {
return res.redirect(redirectTo)
}
res.status(status || 200).json({
data,
errors,
})
} catch (error) {
const output = normalizeApiError(error)
if (output instanceof Response) {
return res.end(output.body)
}
const { status = 500, ...rest } = normalizeApiError(error)
res.status(status).json(rest)
}
}
}

View File

@ -1,11 +0,0 @@
import type { ZodSchema } from 'zod'
import { APIResponse } from './types'
export const parseOutput = <T>(res: APIResponse<T>, parser: ZodSchema) => {
if (res.data) {
res.data = parser.parse(res.data)
}
return res
}
export default parseOutput

View File

@ -1,5 +1,4 @@
import { NextRequest } from 'next/server'
import type { Geo } from '@vercel/edge'
import type { NextRequest } from 'next/server'
import type { CommerceAPI } from '..'
export type ErrorData = { message: string; code?: string }
@ -27,7 +26,6 @@ export type APIHandlerContext<
config: C['provider']['config']
handlers: H
options: Options
geolocation: Geo
}
export type APIHandler<

View File

@ -1,13 +1,10 @@
import type { NextRequest } from 'next/server'
import type { APIHandler } from './types'
import validateMethod, { HTTP_METHODS } from './validate-method'
import { APIHandler } from './types'
/**
* Validates the request method and throws an error if it's not allowed, or if the handler is not implemented.
* and stops the execution of the handler.
* @param req The request object.
* @param res The response object.
* @param allowedOperations An object containing the handlers for each method.
* @throws Error when the method is not allowed or the handler is not implemented.
*/

View File

@ -1,9 +1,9 @@
import { NextRequest, NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
import { CommerceAPIResponseError } from './errors'
export type HTTP_METHODS = 'OPTIONS' | 'GET' | 'POST' | 'PUT' | 'DELETE'
export default function isAllowedMethod(
export default function validateMethod(
req: NextRequest,
allowedMethods: HTTP_METHODS[]
) {
@ -14,15 +14,15 @@ export default function isAllowedMethod(
if (!req.method || !methods.includes(req.method)) {
throw new CommerceAPIResponseError(
`The HTTP ${req.method} method is not supported at this route.`,
NextResponse.json(
{
new Response(
JSON.stringify({
errors: [
{
code: 'invalid_method',
message: `The HTTP ${req.method} method is not supported at this route.`,
},
],
},
}),
{
status: 405,
headers: {
@ -36,7 +36,7 @@ export default function isAllowedMethod(
if (req.method === 'OPTIONS') {
throw new CommerceAPIResponseError(
'This is a CORS preflight request.',
new NextResponse(null, {
new Response(null, {
status: 204,
headers: {
Allow: methods.join(', '),

View File

@ -51,7 +51,7 @@
"@vercel/commerce": "workspace:*",
"cookie": "^0.4.1",
"js-cookie": "^3.0.1",
"@tsndr/cloudflare-worker-jwt": "^2.1.0",
"jsonwebtoken": "^8.5.1",
"lodash.debounce": "^4.0.8"
},
"peerDependencies": {
@ -66,6 +66,7 @@
"@types/chec__commerce.js": "^2.8.4",
"@types/cookie": "^0.4.1",
"@types/js-cookie": "^3.0.2",
"@types/jsonwebtoken": "^8.5.7",
"@types/lodash.debounce": "^4.0.6",
"@types/node": "^17.0.8",
"@types/react": "^18.0.14",

View File

@ -1,3 +1,3 @@
export default function getCheckout(...args: any[]) {
export default function getCheckout(..._args: any[]) {
return Promise.resolve({ data: null })
}

View File

@ -3,16 +3,15 @@ import checkoutEndpoint from '@vercel/commerce/api/endpoints/checkout'
import type { CheckoutSchema } from '@vercel/commerce/types/checkout'
import type { CommercejsAPI } from '../..'
import submitCheckout from './submit-checkout'
import getCheckout from './get-checkout'
import submitCheckout from './submit-checkout'
export type CheckoutAPI = GetAPISchema<CommercejsAPI, CheckoutSchema>
export type CheckoutEndpoint = CheckoutAPI['endpoint']
export const handlers: CheckoutEndpoint['handlers'] = {
submitCheckout,
getCheckout,
submitCheckout,
}
const checkoutApi = createEndpoint<CheckoutAPI>({

View File

@ -1,7 +1,9 @@
import type { LoginEndpoint } from '.'
import { serialize } from 'cookie'
import sdkFetcherFunction from '../../utils/sdk-fetch'
import { getDeploymentUrl } from '../../../utils/get-deployment-url'
import type { LoginEndpoint } from '.'
const login: LoginEndpoint['handlers']['login'] = async ({
req,
@ -10,16 +12,15 @@ const login: LoginEndpoint['handlers']['login'] = async ({
const sdkFetcher: typeof sdkFetcherFunction = sdkFetch
const redirectUrl = getDeploymentUrl()
const { searchParams } = new URL(req.url)
try {
const loginToken = searchParams.get('token')
if (!loginToken) {
return { redirectTo: redirectUrl }
}
const { jwt } = await sdkFetcher('customer', 'getToken', loginToken, false)
return {
redirectTo: redirectUrl,
headers: {
'Set-Cookie': serialize(customerCookie, jwt, {
secure: process.env.NODE_ENV === 'production',
@ -28,9 +29,6 @@ const login: LoginEndpoint['handlers']['login'] = async ({
}),
},
}
} catch {
return { redirectTo: redirectUrl }
}
}
export default login

View File

@ -1,16 +1,14 @@
import Cookies from 'js-cookie'
import {
decode,
type JwtData as CoreJwtData,
} from '@tsndr/cloudflare-worker-jwt'
import { SWRHook } from '@vercel/commerce/utils/types'
import useCustomer, {
UseCustomer,
} from '@vercel/commerce/customer/use-customer'
import { CUSTOMER_COOKIE, API_URL } from '../constants'
import type { SWRHook } from '@vercel/commerce/utils/types'
import type { CustomerHook } from '@vercel/commerce/types/customer'
type JwtData = CoreJwtData & {
import Cookies from 'js-cookie'
import { decode, type JwtPayload } from 'jsonwebtoken'
import useCustomer, {
type UseCustomer,
} from '@vercel/commerce/customer/use-customer'
import { CUSTOMER_COOKIE, API_URL } from '../constants'
type JwtData = JwtPayload & {
cid: string
}

View File

@ -30,7 +30,7 @@ const login: LoginEndpoint['handlers']['login'] = async ({
token.accessTokenExpiration ? { expires: cookieExpirationDate } : {}
)
return { data: response, headers: { 'Set-Cookie': authCookie } }
return { data: null, headers: { 'Set-Cookie': authCookie } }
} catch (error) {
// Check if the email and password didn't match an existing account
if (

View File

@ -1,10 +1,9 @@
import { KiboCommerceConfig } from './../index'
import { getCookieExpirationDate } from '../../lib/get-cookie-expiration-date'
import { prepareSetCookie } from '../../lib/prepare-set-cookie'
import { setCookies } from '../../lib/set-cookie'
import getAnonymousShopperToken from './get-anonymous-shopper-token'
import { NextRequest, NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
const parseCookie = (cookieValue?: any) => {
return cookieValue

View File

@ -3,7 +3,6 @@ import type { Provider, SaleorConfig } from '..'
import { throwUserErrors } from '../../utils'
import * as Mutation from '../../utils/mutations'
import type { NextResponse } from 'next/server'
export default function loginOperation({
commerce,
@ -15,7 +14,7 @@ export default function loginOperation({
}: {
query?: string
variables: any
res: NextResponse
res: Response
config?: SaleorConfig
}): Promise<any> {
config = commerce.getConfig(config)

View File

@ -1,22 +0,0 @@
import type { NextApiRequest, NextApiResponse } from 'next'
export default function isAllowedMethod(req: NextApiRequest, res: NextApiResponse, allowedMethods: string[]) {
const methods = allowedMethods.includes('OPTIONS') ? allowedMethods : [...allowedMethods, 'OPTIONS']
if (!req.method || !methods.includes(req.method)) {
res.status(405)
res.setHeader('Allow', methods.join(', '))
res.end()
return false
}
if (req.method === 'OPTIONS') {
res.status(200)
res.setHeader('Allow', methods.join(', '))
res.setHeader('Content-Length', '0')
res.end()
return false
}
return true
}

View File

@ -7,7 +7,6 @@ import {
throwUserErrors,
} from '../../utils'
import { CustomerAccessTokenCreateMutation } from '../../../schema'
import type { NextResponse } from 'next/server'
export default function loginOperation({
commerce,
@ -19,7 +18,7 @@ export default function loginOperation({
}: {
query?: string
variables: T['variables']
res: NextResponse
res: Response
config?: ShopifyConfig
}): Promise<T['data']> {
config = commerce.getConfig(config)

View File

@ -1,4 +1,3 @@
import { NextResponse } from 'next/server'
import type { CheckoutEndpoint } from '.'
const getCheckout: CheckoutEndpoint['handlers']['getCheckout'] = async ({
@ -27,7 +26,7 @@ const getCheckout: CheckoutEndpoint['handlers']['getCheckout'] = async ({
</html>
`
return new NextResponse(html, {
return new Response(html, {
headers: {
'content-type': 'text/html',
},

View File

@ -1 +0,0 @@
export default function () {}

View File

@ -1 +0,0 @@
export default function () {}

View File

@ -1 +0,0 @@
export default function () {}

View File

@ -1 +0,0 @@
export default function () {}

View File

@ -1 +0,0 @@
export default function () {}

View File

@ -1 +0,0 @@
export default function () {}

View File

@ -3,7 +3,7 @@ import type {
OperationOptions,
} from '@vercel/commerce/api/operations'
import type { LoginOperation } from '@vercel/commerce/types/login'
import type { NextResponse } from 'next/server'
import { Provider, SwellConfig } from '..'
export default function loginOperation({
@ -12,14 +12,14 @@ export default function loginOperation({
async function login<T extends LoginOperation>(opts: {
variables: T['variables']
config?: Partial<SwellConfig>
res: NextResponse
res: Response
}): Promise<T['data']>
async function login<T extends LoginOperation>(
opts: {
variables: T['variables']
config?: Partial<SwellConfig>
res: NextResponse
res: Response
} & OperationOptions
): Promise<T['data']>
@ -30,7 +30,7 @@ export default function loginOperation({
}: {
query?: string
variables: T['variables']
res: NextResponse
res: Response
config?: Partial<SwellConfig>
}): Promise<T['data']> {
const config = commerce.getConfig(cfg)

View File

@ -1,2 +0,0 @@
import zeitFetch from '@vercel/fetch'
export default zeitFetch()

View File

@ -1,28 +0,0 @@
import type { NextApiRequest, NextApiResponse } from 'next'
export default function isAllowedMethod(
req: NextApiRequest,
res: NextApiResponse,
allowedMethods: string[]
) {
const methods = allowedMethods.includes('OPTIONS')
? allowedMethods
: [...allowedMethods, 'OPTIONS']
if (!req.method || !methods.includes(req.method)) {
res.status(405)
res.setHeader('Allow', methods.join(', '))
res.end()
return false
}
if (req.method === 'OPTIONS') {
res.status(200)
res.setHeader('Allow', methods.join(', '))
res.setHeader('Content-Length', '0')
res.end()
return false
}
return true
}

View File

@ -7,7 +7,6 @@ import type { LoginOperation } from '@vercel/commerce/types/login'
import type { LoginMutation } from '../../../schema'
import { Provider, VendureConfig } from '..'
import { loginMutation } from '../../utils/mutations/log-in-mutation'
import type { NextResponse } from 'next/server'
export default function loginOperation({
commerce,
@ -15,14 +14,14 @@ export default function loginOperation({
async function login<T extends LoginOperation>(opts: {
variables: T['variables']
config?: Partial<VendureConfig>
res: NextResponse
res: Response
}): Promise<T['data']>
async function login<T extends LoginOperation>(
opts: {
variables: T['variables']
config?: Partial<VendureConfig>
res: NextResponse
res: Response
} & OperationOptions
): Promise<T['data']>
@ -34,7 +33,7 @@ export default function loginOperation({
}: {
query?: string
variables: T['variables']
res: NextResponse
res: Response
config?: Partial<VendureConfig>
}): Promise<T['data']> {
const config = commerce.getConfig(cfg)

22
pnpm-lock.yaml generated
View File

@ -20,6 +20,7 @@ importers:
'@taskr/watch': ^1.1.0
'@tsndr/cloudflare-worker-jwt': ^2.1.0
'@types/cookie': ^0.4.1
'@types/jsonwebtoken': ^8.5.7
'@types/lodash.debounce': ^4.0.6
'@types/node': ^17.0.8
'@types/node-fetch': ^2.6.2
@ -28,6 +29,7 @@ importers:
cookie: ^0.4.1
immutability-helper: ^3.1.1
js-cookie: ^3.0.1
jsonwebtoken: ^8.5.1
lint-staged: ^12.1.7
lodash.debounce: ^4.0.8
next: ^12.0.8
@ -37,6 +39,7 @@ importers:
taskr: ^1.1.0
taskr-swc: ^0.0.1
typescript: ^4.7.4
uuidv4: ^6.2.13
dependencies:
'@cfworker/uuid': 1.12.4
'@tsndr/cloudflare-worker-jwt': 2.1.0
@ -44,12 +47,15 @@ importers:
cookie: 0.4.2
immutability-helper: 3.1.1
js-cookie: 3.0.1
jsonwebtoken: 8.5.1
lodash.debounce: 4.0.8
uuidv4: 6.2.13
devDependencies:
'@taskr/clear': 1.1.0
'@taskr/esnext': 1.1.0
'@taskr/watch': 1.1.0
'@types/cookie': 0.4.1
'@types/jsonwebtoken': 8.5.9
'@types/lodash.debounce': 4.0.7
'@types/node': 17.0.45
'@types/node-fetch': 2.6.2
@ -3193,6 +3199,10 @@ packages:
/@types/scheduler/0.16.2:
resolution: {integrity: sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==}
/@types/uuid/8.3.4:
resolution: {integrity: sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==}
dev: false
/@types/ws/8.5.3:
resolution: {integrity: sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==}
dependencies:
@ -9473,6 +9483,18 @@ packages:
lodash.isplainobject: 4.0.6
dev: false
/uuid/8.3.2:
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
hasBin: true
dev: false
/uuidv4/6.2.13:
resolution: {integrity: sha512-AXyzMjazYB3ovL3q051VLH06Ixj//Knx7QnUSi1T//Ie3io6CpsPu9nVMOx5MoLWh6xV0B9J0hIaxungxXUbPQ==}
dependencies:
'@types/uuid': 8.3.4
uuid: 8.3.2
dev: false
/v8-compile-cache-lib/3.0.1:
resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
dev: true