UNPKG

@foxy.io/sdk

Version:

Universal SDK for a full server-side and a limited in-browser access to Foxy hAPI.

189 lines (188 loc) 10.1 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.API = void 0; const Core = __importStar(require("../core/index.js")); const cross_fetch_1 = require("cross-fetch"); const v8n_js_1 = require("../core/v8n.js"); const fake_storage_1 = __importDefault(require("fake-storage")); /** JS SDK for building backends with [Foxy Hypermedia API](https://api.foxy.io/docs). Hypermedia API is designed to give you complete control over all aspects of your Foxy accounts, whether working with a single store or automating the provisioning of thousands. Anything you can do within the Foxy administration, you can also do through the API. This means that you can embed Foxy into any application (CMS, LMS, CRM, etc.) and expose as much or as little of Foxy's functionality as desired. */ class API extends Core.API { constructor(params) { var _a, _b, _c, _d; API.v8n.classConstructor.check(params); super({ base: (_a = params.base) !== null && _a !== void 0 ? _a : API.BASE_URL, cache: (_b = params.cache) !== null && _b !== void 0 ? _b : new fake_storage_1.default(), fetch: (...args) => this.__fetch(...args), level: params.level, storage: (_c = params.storage) !== null && _c !== void 0 ? _c : new fake_storage_1.default(), }); this.refreshToken = params.refreshToken; this.clientSecret = params.clientSecret; this.clientId = params.clientId; this.version = (_d = params.version) !== null && _d !== void 0 ? _d : API.VERSION; this.__tokenRefreshPromise = null; } static getToken(opts, throwOnFailure = false) { var _a, _b; return __awaiter(this, void 0, void 0, function* () { API.v8n.getAccessToken.check(opts); const headers = new cross_fetch_1.Headers(); const body = new URLSearchParams(); const url = new URL('token', (_a = opts.base) !== null && _a !== void 0 ? _a : API.BASE_URL).toString(); headers.set('FOXY-API-VERSION', (_b = opts.version) !== null && _b !== void 0 ? _b : API.VERSION); headers.set('Content-Type', 'application/x-www-form-urlencoded'); body.set('client_id', opts.clientId); body.set('client_secret', opts.clientSecret); if ('code' in opts) { body.set('code', opts.code); body.set('grant_type', 'authorization_code'); } else { body.set('grant_type', 'refresh_token'); body.set('refresh_token', opts.refreshToken); } const response = yield cross_fetch_1.fetch(url, { body, headers, method: 'POST' }); if (response.ok) return response.json(); if (throwOnFailure) throw new Error(yield response.text()); return null; }); } __fetch(input, init) { var _a, _b, _c; return __awaiter(this, void 0, void 0, function* () { let request = new cross_fetch_1.Request(input, init); let headers = request.headers; const fetchNewAccessToken = () => __awaiter(this, void 0, void 0, function* () { if (this.__tokenRefreshPromise) { this.console.trace('Token refresh already in progress, waiting...'); return this.__tokenRefreshPromise; } this.__tokenRefreshPromise = (() => __awaiter(this, void 0, void 0, function* () { try { this.console.trace('Fetching a new access token...'); const rawToken = yield API.getToken(this, true).catch(err => { this.console.error(err.message); return null; }); if (rawToken) { const token = Object.assign(Object.assign({}, rawToken), { date_created: new Date().toISOString() }); this.storage.setItem(API.ACCESS_TOKEN, JSON.stringify(token)); this.console.info('Access token updated.'); return token; } else { this.console.warn('Failed to fetch access token. Proceeding without authentication.'); return null; } } finally { this.__tokenRefreshPromise = null; } }))(); return this.__tokenRefreshPromise; }); const setHeaders = (accessToken) => { if (!headers.get('Authorization') && accessToken) headers.set('Authorization', `Bearer ${accessToken}`); if (!headers.get('Content-Type')) headers.set('Content-Type', 'application/json'); if (!headers.get('FOXY-API-VERSION')) headers.set('FOXY-API-VERSION', this.version); }; let token = JSON.parse((_a = this.storage.getItem(API.ACCESS_TOKEN)) !== null && _a !== void 0 ? _a : 'null'); if (token) { const expiresAt = new Date(token.date_created).getTime() + token.expires_in * 1000; const refreshAt = Date.now() + API.REFRESH_THRESHOLD; if (expiresAt < refreshAt) { this.storage.removeItem(API.ACCESS_TOKEN); this.console.info('Removed old access token from the storage.'); token = yield fetchNewAccessToken(); } } else { this.console.trace("Access token isn't present in the storage."); token = yield fetchNewAccessToken(); } setHeaders(token === null || token === void 0 ? void 0 : token.access_token); const method = (_c = (_b = init === null || init === void 0 ? void 0 : init.method) === null || _b === void 0 ? void 0 : _b.toUpperCase()) !== null && _c !== void 0 ? _c : 'GET'; this.console.trace(`${method} ${request.url}`); let response = yield cross_fetch_1.fetch(request); if (response.status === 401) { const { error } = (yield response.clone().json()); if (error === 'invalid_token') { this.console.info('Access token is invalid or expired.'); this.storage.removeItem(API.ACCESS_TOKEN); this.console.info('Removed old access token from the storage.'); token = yield fetchNewAccessToken(); if (token) { request = new cross_fetch_1.Request(input, init); headers = request.headers; setHeaders(token.access_token); this.console.trace(`Retrying ${method} ${request.url}`); response = yield cross_fetch_1.fetch(request); } } } return response; }); } } exports.API = API; API.REFRESH_THRESHOLD = 5 * 60 * 1000; API.ACCESS_TOKEN = 'access_token'; API.BASE_URL = new URL('https://api.foxy.io/'); API.VERSION = '1'; API.v8n = { classConstructor: v8n_js_1.v8n().schema({ base: v8n_js_1.v8n().optional(v8n_js_1.v8n().instanceOf(URL)), cache: v8n_js_1.v8n().optional(v8n_js_1.storageV8N), clientId: v8n_js_1.v8n().string(), clientSecret: v8n_js_1.v8n().string(), level: v8n_js_1.v8n().optional(v8n_js_1.v8n().integer()), refreshToken: v8n_js_1.v8n().string(), storage: v8n_js_1.v8n().optional(v8n_js_1.storageV8N), version: v8n_js_1.v8n().optional(v8n_js_1.v8n().passesAnyOf(v8n_js_1.v8n().exact('1'))), }), getAccessToken: v8n_js_1.v8n() .passesAnyOf(v8n_js_1.v8n().schema({ code: v8n_js_1.v8n().string() }), v8n_js_1.v8n().schema({ refreshToken: v8n_js_1.v8n().string() })) .schema({ base: v8n_js_1.v8n().optional(v8n_js_1.v8n().instanceOf(URL)), clientId: v8n_js_1.v8n().string(), clientSecret: v8n_js_1.v8n().string(), version: v8n_js_1.v8n().optional(v8n_js_1.v8n().passesAnyOf(v8n_js_1.v8n().exact('1'))), }), };