UNPKG

@swell/cli

Version:

Swell's command line interface/utility

211 lines (210 loc) 7.05 kB
import fetch from 'node-fetch'; import { stringify } from 'qs'; import config from './config.js'; import { API_BASE_URL, getAdminApiBaseUrl } from './constants.js'; export var HttpMethod; (function (HttpMethod) { HttpMethod["GET"] = "get"; HttpMethod["POST"] = "post"; HttpMethod["PUT"] = "put"; HttpMethod["DELETE"] = "delete"; })(HttpMethod || (HttpMethod = {})); const GET_ALL_LIMIT = 1000; const defaultHeaders = (opts) => ({ 'Content-Type': 'application/json', ...opts, }); const authenticatedHeaders = (storeId, opts = {}) => { let { secretKey, sessionId, ...moreOpts } = opts; sessionId ??= config.getSessionId(storeId); if (secretKey) { return defaultHeaders({ Authorization: `Basic ${Buffer.from(`${storeId}:${secretKey}`).toString('base64')}`, ...moreOpts, }); } if (sessionId) { return defaultHeaders({ 'X-Session': sessionId, ...moreOpts, }); } return defaultHeaders(moreOpts); }; export default class Api { envId; secretKey; storeId; constructor(storeId, envId, secretKey) { this.storeId = storeId; this.envId = envId; this.secretKey = secretKey; } async api(pathOpts, options) { const storeId = this.storeId || config.getDefaultStore(); let path; const fetchOptions = { ...options, headers: { 'User-Agent': 'swell-cli/2.0', ...options?.headers, ...(this.envId ? { 'Swell-Env': this.envId } : undefined), ...authenticatedHeaders(storeId, { secretKey: this.secretKey, sessionId: options.sessionId, }), }, }; path = this.isAdmin() ? `${getAdminApiBaseUrl(storeId)}/${this.truncatePath(pathOpts.adminPath)}` : `${API_BASE_URL}/${this.truncatePath(pathOpts.backendPath)}`; if (options.query) { path += `?${stringify(options.query)}`; } path = path .replace('${STORE_ID}', storeId) // replace store id in template string .replaceAll(/([^:]\/)\/+/g, '$1'); // remove duplicate slashes if (!path) throw new Error('Operation not supported.'); const res = await fetch(path, fetchOptions); if (options.rawResponse) { return res; } const resData = await res.text(); if (!res.ok) { // Try to parse error message let errorJson; try { errorJson = JSON.parse(resData); } catch { // ignore } const error = new Error(errorJson?.error?.message || errorJson?.error || resData); error.status = res.status; throw error; } try { return JSON.parse(resData); } catch (error) { // respData could be "{}" (length: >=2) but it shouldn't be "{", " " (whitespace - length: 1) or // "" (empty response - length: 0) while logging the error. if (resData.length >= 2) console.log(error); return null; } } async get(pathOpts, options = {}) { return this.api(pathOpts, { method: HttpMethod.GET, ...options, }); } async getAll(pathOpts, options = {}) { const result = { results: [], count: 0, }; let page = 1; do { // eslint-disable-next-line no-await-in-loop const pageResult = await this.api(pathOpts, { method: HttpMethod.GET, ...options, query: { limit: GET_ALL_LIMIT, ...options.query, page, }, }); if (pageResult && pageResult.results) { result.results = [...result.results, ...pageResult.results]; result.count = pageResult.count; page++; } else { break; } } while (result.count > result.results.length && result.results.length > 0); return result; } async post(pathOpts, options = {}) { // ensure post body is stringified if (options.body) options.body = JSON.stringify(options.body); const response = await this.api(pathOpts, { method: HttpMethod.POST, ...options, }); return this.waitForAsyncResponse(response, pathOpts, options); } async put(pathOpts, options = {}) { // ensure post body is stringified if (options.body) options.body = JSON.stringify(options.body); const response = await this.api(pathOpts, { method: HttpMethod.PUT, ...options, }); return this.waitForAsyncResponse(response, pathOpts, options); } async delete(pathOpts, options = {}) { // ensure post body is stringified if (options.body) options.body = JSON.stringify(options.body); const response = await this.api(pathOpts, { method: HttpMethod.DELETE, ...options, }); return this.waitForAsyncResponse(response, pathOpts, options); } async isTestEnvEnabled() { const clientRecord = await this.get({ adminPath: `/client`, }); return clientRecord.test_enabled !== false; } async setEnv(envId) { const hasTestEnv = await this.isTestEnvEnabled(); if (!hasTestEnv) { throw new Error('Test environment is not enabled for this store.'); } this.envId = envId; } async setStoreEnv(storeId, envId) { if (envId) { await this.setEnv(envId); } this.storeId = storeId; } isAdmin() { return !this.secretKey; } truncatePath(path) { return path?.startsWith('/') ? path.slice(1) : path; } async waitForAsyncResponse(response, pathOpts, options = {}) { const { onAsyncGetPath, spinner } = options; if (!response?.async_progressing || !onAsyncGetPath) { return response; } await new Promise((resolve) => { setTimeout(resolve, 1000); }); const asyncResponse = await this.api(onAsyncGetPath(response), { method: HttpMethod.GET, onAsyncGetPath, }); if (asyncResponse?.async_error) { throw new Error(asyncResponse.async_error); } if (!asyncResponse?.async_progressing && spinner) { spinner.suffixText = ''; } else if (asyncResponse?.async_progress > 0 && spinner) { spinner.suffixText = `${asyncResponse.async_progress}%`; } return this.waitForAsyncResponse(asyncResponse, pathOpts, options); } }