@scayle/storefront-core
Version:
Collection of essential utilities to work with the Storefront API
220 lines (219 loc) • 6.75 kB
JavaScript
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
});
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 })
})
);
}
}