UNPKG

@bookla-app/react-client-sdk

Version:
746 lines (730 loc) 26 kB
'use strict'; var zod = require('zod'); class InterceptorManager { constructor() { this.interceptors = []; } use(interceptor) { this.interceptors.push(interceptor); return () => { const index = this.interceptors.indexOf(interceptor); if (index !== -1) { this.interceptors.splice(index, 1); } }; } async execute(config) { let result = config; for (const interceptor of this.interceptors) { result = await interceptor(result); } return result; } } class BooklaError extends Error { constructor(message, code, status) { super(message); this.code = code; this.status = status; this.name = "BooklaError"; } } class BooklaValidationError extends BooklaError { constructor(message, details) { super(message, "VALIDATION_ERROR"); this.details = details; this.name = "BooklaValidationError"; } } class CancelToken { constructor() { this.isCancelled = false; } cancel() { this.isCancelled = true; } get cancelled() { return this.isCancelled; } } const storage = { setTokens(tokens) { localStorage.setItem("bookla_access_token", tokens.accessToken); localStorage.setItem("bookla_refresh_token", tokens.refreshToken); localStorage.setItem("bookla_expires_at", tokens.expiresAt); }, getTokens() { const accessToken = localStorage.getItem("bookla_access_token"); const refreshToken = localStorage.getItem("bookla_refresh_token"); const expiresAt = localStorage.getItem("bookla_expires_at"); if (accessToken && refreshToken && expiresAt) { return { accessToken, refreshToken, expiresAt }; } return null; }, clearTokens() { localStorage.removeItem("bookla_access_token"); localStorage.removeItem("bookla_refresh_token"); localStorage.removeItem("bookla_expires_at"); }, }; const ENDPOINTS = { bookings: { list: { path: "/v1/client/bookings", auth: "bearer" }, get: { path: "/v1/client/bookings/{id}", auth: "bearer" }, create: { path: "/v1/client/bookings", auth: "apiKeyOrBearer" }, cancel: { path: "/v1/client/bookings/{id}/cancel", auth: "bearer", }, }, services: { list: { path: "/v1/client/companies/{companyId}/services", auth: "apiKey", }, get: { path: "/v1/client/companies/{companyId}/services/{id}", auth: "apiKey", }, getTimes: { path: "/v1/client/companies/{companyId}/services/{id}/times", auth: "apiKey", }, getDates: { path: "/v1/client/companies/{companyId}/services/{id}/dates", auth: "apiKey", }, }, resources: { list: { path: "/v1/client/companies/{companyId}/resources", auth: "apiKey", }, get: { path: "/v1/client/companies/{companyId}/resources/{id}", auth: "apiKey", }, }, subscriptions: { cart: { get: { path: "/v1/companies/{companyId}/plugins/subscription/client/cart", auth: "bearer", }, add: { path: "/v1/companies/{companyId}/plugins/subscription/client/cart", auth: "bearer", }, remove: { path: "/v1/companies/{companyId}/plugins/subscription/client/cart/{itemId}", auth: "bearer", }, checkout: { path: "/v1/companies/{companyId}/plugins/subscription/client/cart/checkout", auth: "bearer", }, }, purchases: { list: { path: "/v1/companies/{companyId}/plugins/subscription/client/purchases", auth: "bearer", }, get: { path: "/v1/companies/{companyId}/plugins/subscription/client/purchases/{itemId}", auth: "bearer", }, renew: { path: "/v1/companies/{companyId}/plugins/subscription/client/purchases/renew", auth: "bearer", }, create: { path: "/v1/companies/{companyId}/plugins/subscription/client/purchases", auth: "apiKeyOrBearer", }, }, available: { path: "/v1/companies/{companyId}/plugins/subscription/client/subscriptions", auth: "apiKey", }, }, giftCards: { purchases: { list: { path: "/v1/companies/{companyId}/plugins/giftcards/client/orders", auth: "bearer", }, get: { path: "/v1/companies/{companyId}/plugins/giftcards/client/orders/{itemId}", auth: "bearer", }, create: { path: "/v1/companies/{companyId}/plugins/giftcards/client/orders", auth: "apiKeyOrBearer", }, }, available: { path: "/v1/companies/{companyId}/plugins/giftcards/client/giftcards", auth: "apiKey", }, }, codes: { validate: { path: "/v1/client/codes/{code}/validate", auth: "apiKeyOrBearer", }, }, auth: { refresh: { path: "/v1/client/auth/refresh", auth: "bearer", }, }, }; class HttpClient { constructor(config) { this.tokens = null; this.interceptors = { request: new InterceptorManager(), response: new InterceptorManager(), }; this.baseURL = config.apiUrl; this.apiKey = config.apiKey; this.timeout = config.timeout || 30000; this.retry = config.retry || { maxAttempts: 3, delayMs: 1000, statusCodesToRetry: [408, 429, 500, 502, 503, 504], }; this.debug = config.debug || false; } setTokens(tokens) { this.tokens = tokens; this.saveTokensToStorage(tokens); } async isAuthenticated() { var _a; if ((_a = this.tokens) === null || _a === void 0 ? void 0 : _a.accessToken) { const expiresAt = Date.parse(this.tokens.expiresAt); if (expiresAt < Date.now()) { const refreshed = await this.refreshToken(); if (!refreshed) { this.clearAuth(); return { isAuthenticated: false }; } return { isAuthenticated: true, accessToken: this.tokens.accessToken, expiresAt: Date.parse(this.tokens.expiresAt), }; } return { isAuthenticated: true, accessToken: this.tokens.accessToken, expiresAt: Date.parse(this.tokens.expiresAt), }; } // Check localStorage as fallback const storedTokens = this.loadTokensFromStorage(); if (storedTokens) { this.tokens = storedTokens; // Update current tokens const expiresAt = Date.parse(storedTokens.expiresAt); if (expiresAt < Date.now()) { const refreshed = await this.refreshToken(); if (!refreshed) { this.clearAuth(); return { isAuthenticated: false }; } return { isAuthenticated: true, accessToken: this.tokens.accessToken, expiresAt: Date.parse(this.tokens.expiresAt), }; } return { isAuthenticated: true, accessToken: storedTokens.accessToken, expiresAt: expiresAt, }; } return { isAuthenticated: false }; } clearAuth() { this.tokens = null; storage.clearTokens(); } saveTokensToStorage(tokens) { storage.setTokens(tokens); } loadTokensFromStorage() { return storage.getTokens(); } validateAuth(config) { var _a; if (config.auth === "bearer" && !((_a = this.tokens) === null || _a === void 0 ? void 0 : _a.accessToken)) { throw new BooklaError("Authentication required for this endpoint", "AUTH_REQUIRED"); } } async request(config) { var _a; this.validateAuth(config); let attempt = 0; while (attempt < this.retry.maxAttempts) { try { // Check for cancellation if ((_a = config.cancelToken) === null || _a === void 0 ? void 0 : _a.cancelled) { throw new BooklaError("Request cancelled", "REQUEST_CANCELLED"); } // Apply request interceptors const modifiedConfig = await this.interceptors.request.execute({ ...config, headers: this.getHeaders(config), }); // Make request if (this.debug) { console.log(`[Bookla SDK] Making request:`, modifiedConfig); } const response = await fetch(`${this.baseURL}${config.url}`, { method: config.method, headers: modifiedConfig.headers, body: config.data ? JSON.stringify(config.data) : undefined, signal: AbortSignal.timeout(config.timeout || this.timeout), }); // Handle response if (!response.ok) { throw new BooklaError(response.statusText, "API_ERROR", response.status); } const data = await response.json(); // Apply response interceptors const modifiedResponse = await this.interceptors.response.execute(data); if (this.debug) { console.log(`[Bookla SDK] Response received:`, modifiedResponse); } return modifiedResponse; } catch (error) { // Handle specific error cases if (error instanceof BooklaError) { if (error.code === "REQUEST_CANCELLED") { throw error; } if (error.status === 401) { const refreshed = await this.refreshToken(); if (!refreshed) { throw new BooklaError("Token refresh failed", "TOKEN_REFRESH_FAILED"); } // Retry with new token attempt++; continue; } if (!this.retry.statusCodesToRetry.includes(error.status || 0)) { throw error; } } // Retry logic attempt++; if (attempt === this.retry.maxAttempts) { throw new BooklaError("Max retry attempts reached", "MAX_RETRIES_EXCEEDED"); } await new Promise((resolve) => setTimeout(resolve, this.retry.delayMs * attempt)); } } throw new BooklaError("Request failed", "REQUEST_FAILED"); } getHeaders(config) { var _a; const headers = { "Content-Type": "application/json", }; if ((config.auth === "bearer" || config.auth === "apiKeyOrBearer") && ((_a = this.tokens) === null || _a === void 0 ? void 0 : _a.accessToken)) { headers["Authorization"] = `Bearer ${this.tokens.accessToken}`; } else { headers["X-API-Key"] = this.apiKey; } return headers; } async refreshToken() { var _a; try { if (!((_a = this.tokens) === null || _a === void 0 ? void 0 : _a.refreshToken)) { return false; } const response = await fetch(`${this.baseURL}${ENDPOINTS.auth.refresh.path}`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${this.tokens.refreshToken}`, }, }); if (!response.ok) { return false; } const newTokens = await response.json(); this.setTokens(newTokens); return true; } catch (error) { return false; } } async get(endpoint, options) { return this.request({ method: "GET", url: endpoint.path, auth: endpoint.auth, ...options, }); } async post(endpoint, data, options) { return this.request({ method: "POST", url: endpoint.path, data, auth: endpoint.auth, ...options, }); } async delete(endpoint, options) { return this.request({ method: "DELETE", url: endpoint.path, auth: endpoint.auth, ...options, }); } createCancelToken() { return new CancelToken(); } isCancelledError(error) { return error instanceof BooklaError && error.code === "REQUEST_CANCELLED"; } } class BookingsService { constructor(client) { this.client = client; } async list(options) { return this.client.get(ENDPOINTS.bookings.list, options); } async get(id, options) { return this.client.get({ ...ENDPOINTS.bookings.get, path: ENDPOINTS.bookings.get.path.replace("{id}", id), }, options); } async request(data, options) { this.validateCreateBookingRequest(data); return this.client.post(ENDPOINTS.bookings.create, data, options); } async cancel(bookingId, reason, options) { return this.client.post({ ...ENDPOINTS.bookings.cancel, path: ENDPOINTS.bookings.cancel.path.replace("{Id}", bookingId), }, { reason }, options); } validateCreateBookingRequest(data) { const schema = zod.z.object({ companyID: zod.z.string().min(1), serviceID: zod.z.string().min(1), resourceID: zod.z.string().optional(), startTime: zod.z.string().datetime(), spots: zod.z.number().optional(), duration: zod.z.string().optional(), metaData: zod.z.record(zod.z.unknown()).optional(), pluginData: zod.z.record(zod.z.unknown()).optional(), tickets: zod.z.record(zod.z.number()).optional(), customPurchaseDescription: zod.z.string().optional(), client: zod.z .object({ id: zod.z.string().optional(), booklaID: zod.z.string().optional(), firstName: zod.z.string().optional(), lastName: zod.z.string().optional(), email: zod.z.string().email().optional(), }) .nullish(), }); return schema.parse(data); } } class ServicesService { constructor(client) { this.client = client; } async list(companyId, options) { return this.client.get({ ...ENDPOINTS.services.list, path: ENDPOINTS.services.list.path.replace("{companyId}", companyId), }, options); } async get(companyId, serviceId, options) { return this.client.get({ ...ENDPOINTS.services.list, path: ENDPOINTS.services.get.path .replace("{companyId}", companyId) .replace("{id}", serviceId), }, options); } async getTimes(companyId, serviceId, data, options) { this.validateGetTimesRequest(data); return this.client.post({ ...ENDPOINTS.services.getTimes, path: ENDPOINTS.services.getTimes.path .replace("{companyId}", companyId) .replace("{id}", serviceId), }, data, options); } async getDates(companyId, serviceId, data, options) { this.validateGetDatesRequest(data); return this.client.post({ ...ENDPOINTS.services.getDates, path: ENDPOINTS.services.getDates.path .replace("{companyId}", companyId) .replace("{id}", serviceId), }, data, options); } validateGetTimesRequest(data) { const schema = zod.z.object({ from: zod.z.string().datetime(), to: zod.z.string().datetime(), duration: zod.z.string().optional(), spots: zod.z.number().optional(), resourceIDs: zod.z.array(zod.z.string()).optional(), tickets: zod.z.record(zod.z.number()).optional(), }); return schema.parse(data); } validateGetDatesRequest(data) { const schema = zod.z.object({ from: zod.z.string().datetime(), to: zod.z.string().datetime(), duration: zod.z.string().optional(), spots: zod.z.number().optional(), resourceIDs: zod.z.array(zod.z.string()).optional(), tickets: zod.z.record(zod.z.number()).optional(), }); return schema.parse(data); } } class ResourcesService { constructor(client) { this.client = client; } async list(companyId, options) { return this.client.get({ ...ENDPOINTS.resources.list, path: ENDPOINTS.resources.list.path.replace("{companyId}", companyId), }, options); } async get(companyId, resourceId, options) { return this.client.get({ ...ENDPOINTS.resources.get, path: ENDPOINTS.resources.get.path .replace("{companyId}", companyId) .replace("{id}", resourceId), }, options); } } class CodesService { constructor(client) { this.client = client; } async validateCode(code, data, options) { this.validateCodeRequest(data); return this.client.post({ ...ENDPOINTS.codes.validate, path: ENDPOINTS.codes.validate.path.replace("{code}", code), }, data, options); } validateCodeRequest(data) { const schema = zod.z.object({ companyID: zod.z.string().min(1), serviceID: zod.z.string().min(1), resourceID: zod.z.string().min(1), startTime: zod.z.string().datetime(), duration: zod.z.string().optional(), spots: zod.z.number().min(1), tickets: zod.z.record(zod.z.number()).optional(), }); return schema.parse(data); } } class ClientSubscriptionService { constructor(client) { this.client = client; } validateAddToCartRequest(data) { const schema = zod.z.object({ items: zod.z.array(zod.z.object({ subscriptionID: zod.z.string().min(1), metaData: zod.z.record(zod.z.unknown()).optional(), })), }); return schema.parse(data); } validateRenewRequest(data) { const schema = zod.z.object({ ids: zod.z.array(zod.z.string().min(1)), }); return schema.parse(data); } async getCart(companyId, options) { return this.client.get({ ...ENDPOINTS.subscriptions.cart.get, path: ENDPOINTS.subscriptions.cart.get.path.replace("{companyId}", companyId), }, options); } async addToCart(companyId, data, options) { this.validateAddToCartRequest(data); return this.client.post({ ...ENDPOINTS.subscriptions.cart.add, path: ENDPOINTS.subscriptions.cart.add.path.replace("{companyId}", companyId), }, data, options); } async removeFromCart(companyId, itemId, options) { return this.client.delete({ ...ENDPOINTS.subscriptions.cart.remove, path: ENDPOINTS.subscriptions.cart.remove.path .replace("{companyId}", companyId) .replace("{itemId}", itemId), }, options); } async checkout(companyId, options) { return this.client.post({ ...ENDPOINTS.subscriptions.cart.checkout, path: ENDPOINTS.subscriptions.cart.checkout.path.replace("{companyId}", companyId), }, undefined, options); } async getPurchases(companyId, options) { return this.client.get({ ...ENDPOINTS.subscriptions.purchases.list, path: ENDPOINTS.subscriptions.purchases.list.path.replace("{companyId}", companyId), }, options); } async getPurchase(companyId, itemId, options) { return this.client.get({ ...ENDPOINTS.subscriptions.purchases.get, path: ENDPOINTS.subscriptions.purchases.get.path .replace("{companyId}", companyId) .replace("{itemId}", itemId), }, options); } async renewPurchases(companyId, data, options) { this.validateRenewRequest(data); return this.client.post({ ...ENDPOINTS.subscriptions.purchases.renew, path: ENDPOINTS.subscriptions.purchases.renew.path.replace("{companyId}", companyId), }, data, options); } async getAvailableSubscriptions(companyId, ids, options) { const queryParams = ids ? `?ids=${ids.join(",")}` : ""; return this.client.get({ ...ENDPOINTS.subscriptions.available, path: `${ENDPOINTS.subscriptions.available.path.replace("{companyId}", companyId)}${queryParams}`, }, options); } async createPurchase(companyId, data, options) { const schema = zod.z.object({ items: zod.z.array(zod.z.object({ subscriptionID: zod.z.string().min(1), metaData: zod.z.record(zod.z.unknown()).optional(), })), client: zod.z .object({ id: zod.z.string().optional(), booklaID: zod.z.string().optional(), firstName: zod.z.string().optional(), lastName: zod.z.string().optional(), email: zod.z.string().email().optional(), }) .nullish(), }); const validatedData = schema.parse(data); return this.client.post({ ...ENDPOINTS.subscriptions.purchases.create, path: ENDPOINTS.subscriptions.purchases.create.path.replace("{companyId}", companyId), }, validatedData, options); } } class ClientGiftCardsService { constructor(client) { this.client = client; } async getPurchases(companyId, options) { return this.client.get({ ...ENDPOINTS.giftCards.purchases.list, path: ENDPOINTS.giftCards.purchases.list.path.replace("{companyId}", companyId), }, options); } async getPurchase(companyId, itemId, options) { return this.client.get({ ...ENDPOINTS.giftCards.purchases.get, path: ENDPOINTS.giftCards.purchases.get.path .replace("{companyId}", companyId) .replace("{itemId}", itemId), }, options); } async getAvailableGiftCards(companyId, ids, options) { const queryParams = ids ? `?ids=${ids.join(",")}` : ""; return this.client.get({ ...ENDPOINTS.giftCards.available, path: `${ENDPOINTS.giftCards.available.path.replace("{companyId}", companyId)}${queryParams}`, }, options); } async createPurchase(companyId, data, options) { const schema = zod.z.object({ giftCardID: zod.z.string().min(1), metaData: zod.z.record(zod.z.unknown()).optional(), client: zod.z .object({ id: zod.z.string().optional(), booklaID: zod.z.string().optional(), firstName: zod.z.string().optional(), lastName: zod.z.string().optional(), email: zod.z.string().email().optional(), }) .nullish(), }); const validatedData = schema.parse(data); return this.client.post({ ...ENDPOINTS.giftCards.purchases.create, path: ENDPOINTS.giftCards.purchases.create.path.replace("{companyId}", companyId), }, validatedData, options); } } class BooklaSDK { constructor(config) { this.client = new HttpClient(config); this.bookings = new BookingsService(this.client); this.services = new ServicesService(this.client); this.resources = new ResourcesService(this.client); this.codes = new CodesService(this.client); this.subscriptions = new ClientSubscriptionService(this.client); this.giftCards = new ClientGiftCardsService(this.client); } setAuthTokens(tokens) { this.client.setTokens(tokens); } async isAuthenticated() { return this.client.isAuthenticated(); } clearAuth() { return this.client.clearAuth(); } get interceptors() { return this.client.interceptors; } createCancelToken() { return this.client.createCancelToken(); } isCancelledError(error) { return this.client.isCancelledError(error); } } exports.BooklaError = BooklaError; exports.BooklaSDK = BooklaSDK; exports.BooklaValidationError = BooklaValidationError; exports.CancelToken = CancelToken; //# sourceMappingURL=index.js.map