UNPKG

@shopify/cli-kit

Version:

A set of utilities, interfaces, and models that are common across all the platform features

174 lines • 7.15 kB
import { graphqlRequest, graphqlRequestDoc, } from './graphql.js'; import { outputContent, outputToken } from '../../../public/node/output.js'; import { AbortError, BugError } from '../error.js'; import { restRequestBody, restRequestHeaders, restRequestUrl, isThemeAccessSession, } from '../../../private/node/api/rest.js'; import { shopifyFetch } from '../http.js'; import { PublicApiVersions } from '../../../cli/api/graphql/admin/generated/public_api_versions.js'; import { normalizeStoreFqdn } from '../context/fqdn.js'; import { themeKitAccessDomain } from '../../../private/node/constants.js'; import { serviceEnvironment } from '../../../private/node/context/service.js'; import { DevServerCore } from '../vendor/dev_server/index.js'; import { ClientError } from 'graphql-request'; const LatestApiVersionByFQDN = new Map(); /** * Executes a GraphQL query against the Admin API. * * @param query - GraphQL query to execute. * @param session - Shopify admin session including token and Store FQDN. * @param variables - GraphQL variables to pass to the query. * @returns The response of the query of generic type <T>. */ export async function adminRequest(query, session, variables) { const api = 'Admin'; const version = await fetchLatestSupportedApiVersion(session); let storeDomain = await normalizeStoreFqdn(session.storeFqdn); const addedHeaders = themeAccessHeaders(session); if (serviceEnvironment() === 'local') { addedHeaders['x-forwarded-host'] = storeDomain; storeDomain = new DevServerCore().host('app'); } const url = adminUrl(storeDomain, version, session); return graphqlRequest({ query, api, addedHeaders, url, token: session.token, variables }); } /** * Executes a GraphQL query against the Admin API. Uses typed documents. * * @param options - Admin request options. * @returns The response of the query of generic type <TResult>. */ export async function adminRequestDoc(options) { const { query, session, variables, version, responseOptions, requestBehaviour } = options; let apiVersion = version ?? LatestApiVersionByFQDN.get(session.storeFqdn); if (!apiVersion) { apiVersion = await fetchLatestSupportedApiVersion(session); } let storeDomain = await normalizeStoreFqdn(session.storeFqdn); const addedHeaders = themeAccessHeaders(session); if (serviceEnvironment() === 'local') { addedHeaders['x-forwarded-host'] = storeDomain; storeDomain = new DevServerCore().host('app'); } const opts = { url: adminUrl(storeDomain, apiVersion, session), api: 'Admin', token: session.token, addedHeaders, }; let unauthorizedHandler; if ('refresh' in session) { unauthorizedHandler = { type: 'token_refresh', handler: session.refresh }; } const result = graphqlRequestDoc({ ...opts, query, variables, responseOptions, unauthorizedHandler, requestBehaviour, }); return result; } function themeAccessHeaders(session) { return isThemeAccessSession(session) ? { 'X-Shopify-Shop': session.storeFqdn, 'X-Shopify-Access-Token': session.token } : {}; } /** * GraphQL query to retrieve the latest supported API version. * * @param session - Shopify admin session including token and Store FQDN. * @returns - The latest supported API version. */ async function fetchLatestSupportedApiVersion(session) { const apiVersions = await supportedApiVersions(session); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const latest = apiVersions.reverse()[0]; LatestApiVersionByFQDN.set(session.storeFqdn, latest); return latest; } /** * GraphQL query to retrieve all supported API versions. * * @param session - Shopify admin session including token and Store FQDN. * @returns - An array of supported API versions. */ export async function supportedApiVersions(session) { const apiVersions = await fetchApiVersions(session); return apiVersions .filter((item) => item.supported) .map((item) => item.handle) .sort(); } /** * GraphQL query to retrieve all API versions. * * @param session - Shopify admin session including token and Store FQDN. * @returns - An array of supported and unsupported API versions. */ async function fetchApiVersions(session) { try { const response = await adminRequestDoc({ query: PublicApiVersions, session, variables: {}, version: 'unstable', responseOptions: { handleErrors: false }, }); return response.publicApiVersions; } catch (error) { if (error instanceof ClientError && error.response.status === 403) { const storeName = session.storeFqdn.replace('.myshopify.com', ''); throw new AbortError(outputContent `Looks like you don't have access this dev store: (${outputToken.link(storeName, `https://${session.storeFqdn}`)})`, outputContent `If you're not the owner, create a dev store staff account for yourself`); } if (error instanceof ClientError && (error.response.status === 401 || error.response.status === 404)) { throw new AbortError(`Error connecting to your store ${session.storeFqdn}: ${error.message} ${error.response.status} ${error.response.data}`); } else { throw new BugError(`Unknown error connecting to your store ${session.storeFqdn}: ${error instanceof Error ? error.message : String(error)}`); } } } /** * Returns the Admin API URL for the given store and version. * * @param store - Store FQDN. * @param version - API version. * @param session - User session. * @returns - Admin API URL. */ export function adminUrl(store, version, session) { const realVersion = version ?? 'unstable'; const url = session && isThemeAccessSession(session) ? `https://${themeKitAccessDomain}/cli/admin/api/${realVersion}/graphql.json` : `https://${store}/admin/api/${realVersion}/graphql.json`; return url; } /** * Executes a REST request against the Admin API. * * @param method - Request's HTTP method. * @param path - Path of the REST resource. * @param session - Shopify Admin session including token and Store FQDN. * @param requestBody - Request body of including REST resource specific parameters. * @param searchParams - Search params, appended to the URL. * @param apiVersion - Admin API version. * @returns - The {@link RestResponse}. */ export async function restRequest(method, path, session, requestBody, searchParams = {}, apiVersion = 'unstable') { const url = restRequestUrl(session, apiVersion, path, searchParams); const body = restRequestBody(requestBody); const headers = restRequestHeaders(session); const response = await shopifyFetch(url, { headers, method, body, }); const json = await response.json().catch(() => ({})); return { json, status: response.status, headers: response.headers.raw(), }; } //# sourceMappingURL=admin.js.map