UNPKG

@scayle/storefront-core

Version:

Collection of essential utilities to work with the Storefront API

220 lines (219 loc) 6.76 kB
import { FetchError } from "../utils/fetch.mjs"; import { HttpStatusCode } from "../constants/httpStatus.mjs"; import { getOAuthClient } from "./oauth.mjs"; export class CustomerAPIClient { /** * Base URL for the Checkout API. */ baseURL; /** * RPC context containing authentication and other information. */ context; /** * Additional headers to include in requests. */ additionalHeaders; /** * Creates a new instance of the CustomerAPIClient. * * @param context The RPC context. */ constructor(context) { this.baseURL = context.checkout.url; this.context = context; this.additionalHeaders = context.internalAccessHeader ? { "x-internal-access": context.internalAccessHeader } : {}; } /** * Gets the headers for API requests, including authorization. */ get headers() { return { Authorization: `Bearer ${this.context.accessToken}`, Accept: "application/json", "Content-Type": "application/json", ...this.additionalHeaders }; } /** * Sends an API request and handles potential token refresh. * * @param request The request function to execute. * @param retry Whether to retry the request after a token refresh. * * @returns The parsed JSON response. * * @template BodyType The expected type of the response body. */ async sendRequest(request, retry = true) { const response = await request(); if (response.ok) { return await response.json(); } if ((response.status === HttpStatusCode.UNAUTHORIZED && response.headers.get("WWW-Authenticate")?.includes("invalid_token") || response.status === HttpStatusCode.FORBIDDEN) && this.context.accessToken && this.context.refreshToken) { if (retry) { const client = getOAuthClient(this.context); try { const tokens = await client.refreshToken({ grant_type: "refresh_token", refresh_token: this.context.refreshToken }); await this.context.updateTokens({ accessToken: tokens?.access_token, refreshToken: tokens?.refresh_token }); } catch (e) { if (e instanceof FetchError && e.response.status === HttpStatusCode.UNAUTHORIZED) { this.context.log.debug( "Failed to refresh Checkout Token due to invalid refresh token. Deleting session" ); await this.context.destroySession(); } else { this.context.log.debug( "Failed to refresh Checkout Token for unknown reason." ); } throw new FetchError(response); } return await this.sendRequest(request, false); } else { this.context.log.debug("Invalid Checkout Token. Deleting session"); await this.context.destroySession(); } } throw new FetchError(response); } /** * Get the addresses for the current customer. * * @param shopId The ID of the shop. * * @returns A paginated response containing the customer's addresses. * * @see https://scayle.dev/en/api-guides/customer-account-api/resources/customer/address/list-addresses */ async getAddresses(shopId) { return await this.sendRequest( () => fetch(`${this.baseURL}/api/oauth/customer/addresses`, { headers: { ...this.headers, "X-Shop-Id": shopId.toString() } }) ); } /** * Returns customer data and latest orders. * When not logged in, it will return `undefined`. * * @param shopId The ID of the shop. * @param accessToken Optional access token to use for the request. * @returns The customer data. * * @see https://scayle.dev/en/api-guides/customer-account-api/resources/customer/get-customer */ async getMe(shopId, accessToken) { return await this.sendRequest( () => fetch(`${this.baseURL}/api/oauth/me`, { headers: { ...this.headers, ...accessToken ? { Authorization: `Bearer ${accessToken}` } : {}, "X-Shop-Id": shopId.toString() } }) ); } /** * Retrieve a customer's order. * * @param shopId The ID of the shop. * @param orderId The ID of the order. * * @returns The customer's order. * * @see https://scayle.dev/en/api-guides/customer-account-api/resources/order/get-order */ async getOrder(shopId, orderId) { return this.sendRequest( () => fetch(`${this.baseURL}/api/oauth/customer/order/${orderId}`, { headers: { ...this.headers, "X-Shop-Id": shopId.toString() } }) ); } /** * Update contact details of a customer. * * @param shopId The ID of the shop. * @param data Contact data to update. * @param data.email Customer's email address. * @param data.phone Customer's phone number. * * @returns The updated customer data. * * @see https://scayle.dev/en/api-guides/customer-account-api/resources/customer/update-contact-details */ async updateContactInfo(shopId, { email, phone }) { return await this.sendRequest( () => fetch(`${this.baseURL}/api/oauth/customer/contact`, { method: "PUT", headers: { ...this.headers, "X-Shop-Id": shopId.toString() }, body: JSON.stringify({ email, phone }) }) ); } /** * Update customer personal data. * * @param shopId The ID of the shop. * @param payload Personal data to update. * * @returns The updated customer data. * * @see https://scayle.dev/en/api-guides/customer-account-api/resources/customer/update-personal-data */ async updatePersonalInfo(shopId, payload) { return await this.sendRequest( () => fetch(`${this.baseURL}/api/oauth/customer/personal`, { method: "PUT", headers: { ...this.headers, "X-Shop-Id": shopId.toString() }, body: JSON.stringify(payload) }) ); } /** * Update customer password. * * @param shopId The ID of the shop. * @param data Data for updating the password. * @param data.password Current password. * @param data.newPassword New password. * * @returns The updated customer data. * * @see https://scayle.dev/en/api-guides/customer-account-api/resources/customer/update-password */ async updatePassword(shopId, { password, newPassword }) { return await this.sendRequest( () => fetch(`${this.baseURL}/api/oauth/customer/password`, { method: "PUT", headers: { ...this.headers, "X-Shop-Id": shopId.toString() }, body: JSON.stringify({ password, newPassword }) }) ); } }