import type { Printful } from "./util/types"; import { type DeepPartial, type PagingOptions } from "./util/misc"; const parsePayload = (res: Response): Promise => res.json().catch(() => res.text().catch(() => undefined)); type ClientOptions = { token: string; storeId: string; }; type Credentials = { apiUrl: string; authHeaders: Record; }; export class PrintfulError extends Error { constructor( message: string, public status: number, public payload: unknown, ) { super(message); this.name = "PrintfulError"; } } class VariantsClient { constructor(private creds: Credentials) { } async get(variantId: number | string): Promise { const res = await fetch(`${this.creds.apiUrl}/store/variants/${variantId}`, { method: "GET", headers: this.creds.authHeaders, }); if (res.status === 404 || res.status === 400) return; if (!res.ok) throw new PrintfulError("Failed to get Printful variant", res.status, await parsePayload(res)); const payload = (await res.json()) as Printful.MetaDataSingle; return payload.result; } } class ProductsClient { readonly Variants: VariantsClient; constructor(private creds: Credentials) { this.Variants = new VariantsClient(creds); } async list(opt: PagingOptions = {}): Promise { const allSyncProducts: Printful.Products.SyncProduct[] = []; let offset = opt.offset ?? 0; do { const res = await fetch(`${this.creds.apiUrl}/store/products?offset=${offset}&limit=${opt.limit ?? 100}`, { method: "GET", headers: this.creds.authHeaders, }); if (!res.ok) throw new PrintfulError("Failed to get all Printful products", res.status, await parsePayload(res)); const payload = (await res.json()) as Printful.MetaDataMulti; allSyncProducts.push(...payload.result); offset = allSyncProducts.length; if (allSyncProducts.length >= payload.paging.total) break; } while (opt.forceAll); return allSyncProducts; } async get(productId: number | string): Promise { const res = await fetch(`${this.creds.apiUrl}/store/products/${productId}`, { method: "GET", headers: this.creds.authHeaders, }); if (res.status === 404 || res.status === 400) return; if (!res.ok) throw new PrintfulError("Failed to get Printful product", res.status, await parsePayload(res)); const payload = (await res.json()) as Printful.MetaDataSingle; return payload.result; } async update(printfulProductId: number, printfulProduct: DeepPartial) { const res = await fetch(`${this.creds.apiUrl}/store/products/${printfulProductId}`, { method: "PUT", headers: { "Content-Type": "application/json", ...this.creds.authHeaders, }, body: JSON.stringify(printfulProduct), }); if (!res.ok) throw new PrintfulError("Failed to update Printful product", res.status, await parsePayload(res)); } } class OrdersClient { constructor(private creds: Credentials) { } async create(printfulOrder: Printful.Orders.Order) { const res = await fetch(`${this.creds.apiUrl}/orders`, { method: "POST", headers: { "Content-Type": "application/json", ...this.creds.authHeaders, }, body: JSON.stringify(printfulOrder), }); if (!res.ok) throw new PrintfulError("Failed to create Printful order", res.status, await parsePayload(res)); } async list(opt: PagingOptions = {}): Promise { const allPrintfulOrders: Printful.Orders.Order[] = []; let offset = opt.offset ?? 0; do { const res = await fetch(`${this.creds.apiUrl}/orders?offset=${offset}&limit=${opt.limit ?? 100}`, { method: "GET", headers: this.creds.authHeaders, }); if (!res.ok) throw new PrintfulError("Failed to get all Printful orders", res.status, await parsePayload(res)); const payload = (await res.json()) as Printful.MetaDataMulti; allPrintfulOrders.push(...payload.result); offset = allPrintfulOrders.length; if (allPrintfulOrders.length >= payload.paging.total) break; } while (opt.forceAll); return allPrintfulOrders; } } class WebhooksClient { constructor(private creds: Credentials) { } async getConfig(): Promise { const res = await fetch(`${this.creds.apiUrl}/webhooks`, { method: "GET", headers: { "Content-Type": "application/json", ...this.creds.authHeaders, } }); if (res.status === 404 || res.status === 400) return; if (!res.ok) throw new PrintfulError("Failed to get webhook configuration", res.status, await parsePayload(res)); const payload = (await res.json()) as Printful.Webhook.Config; return payload; } async register(url: string, events: Printful.Webhook.Event[]) { const res = await fetch(`${this.creds.apiUrl}/webhooks`, { method: "POST", headers: { "Content-Type": "application/json", ...this.creds.authHeaders, }, body: JSON.stringify({ "url": url, "types": events }), }); if (!res.ok) throw new PrintfulError("Failed to register webhook URL", res.status, await parsePayload(res)); } } export class PrintfulClient { readonly Products: ProductsClient; readonly Orders: OrdersClient; readonly Webhooks: WebhooksClient; constructor(options: ClientOptions) { const creds: Credentials = { apiUrl: "https://api.printful.com", authHeaders: { Authorization: `Bearer ${options.token}`, "X-PF-Store-Id": options.storeId, }, }; this.Products = new ProductsClient(creds); this.Orders = new OrdersClient(creds); this.Webhooks = new WebhooksClient(creds); } static Util = { getVariantMainImage(syncVariant: Printful.Products.SyncVariant): string { const previewFile = syncVariant.files.find((f) => f.type === "preview"); return previewFile?.preview_url ?? syncVariant.product.image; }, }; }