UNPKG

@shopify/shopify-app-express

Version:

Shopify Express Middleware - to simplify the building of Shopify Apps with Express

178 lines (159 loc) 6.05 kB
import {compare} from 'compare-versions'; import '@shopify/shopify-api/adapters/node'; import { shopifyApi, ConfigParams as ApiConfigParams, Shopify, FeatureDeprecatedError, ShopifyRestResources, } from '@shopify/shopify-api'; import {MemorySessionStorage} from '@shopify/shopify-app-session-storage-memory'; import {SHOPIFY_EXPRESS_LIBRARY_VERSION} from './version'; import {AppConfigInterface, AppConfigParams} from './config-types'; import { validateAuthenticatedSession, cspHeaders, ensureInstalled, redirectToShopifyOrAppRoot, } from './middlewares/index'; import {AuthMiddleware} from './auth/types'; import {auth} from './auth/index'; import {ProcessWebhooksMiddleware} from './webhooks/types'; import {processWebhooks} from './webhooks/index'; import { ValidateAuthenticatedSessionMiddleware, CspHeadersMiddleware, EnsureInstalledMiddleware, RedirectToShopifyOrAppRootMiddleware, } from './middlewares/types'; import {redirectOutOfApp} from './redirect-out-of-app'; import {RedirectOutOfAppFunction} from './types'; export * from './types'; export * from './auth/types'; export * from './middlewares/types'; export * from './webhooks/types'; export type {AppConfigParams, ExpressApiConfigParams} from './config-types'; export {ApiVersion} from '@shopify/shopify-api'; type DefaultedConfigs<Params extends Partial<ApiConfigParams> | undefined> = ApiConfigParams & Params; type ConfigInterfaceFromParams< Params extends AppConfigParams | Omit<AppConfigParams, 'api'>, > = AppConfigInterface< Params extends AppConfigParams ? NonNullable<DefaultedConfigs<Params['api']>['restResources']> : ShopifyRestResources, Params['sessionStorage'] extends undefined ? MemorySessionStorage : NonNullable<Params['sessionStorage']> >; export interface ShopifyApp<Params extends AppConfigParams = AppConfigParams> { config: ConfigInterfaceFromParams<Params>; api: Shopify< DefaultedConfigs<Params['api']>, DefaultedConfigs<Params['api']>['restResources'] & ShopifyRestResources >; auth: AuthMiddleware; processWebhooks: ProcessWebhooksMiddleware; validateAuthenticatedSession: ValidateAuthenticatedSessionMiddleware; cspHeaders: CspHeadersMiddleware; ensureInstalledOnShop: EnsureInstalledMiddleware; redirectToShopifyOrAppRoot: RedirectToShopifyOrAppRootMiddleware; redirectOutOfApp: RedirectOutOfAppFunction; } export function shopifyApp<Params extends AppConfigParams>( config: Params, ): ShopifyApp<Params> { const {api: apiConfig, ...appConfig} = config; const api = shopifyApi(apiConfigWithDefaults(apiConfig)); const validatedConfig = validateAppConfig(appConfig, api); return { config: validatedConfig, api: api as Shopify< DefaultedConfigs<Params['api']>, DefaultedConfigs<Params['api']>['restResources'] & ShopifyRestResources >, auth: auth({api, config: validatedConfig}), processWebhooks: processWebhooks({api, config: validatedConfig}), validateAuthenticatedSession: validateAuthenticatedSession({ api, config: validatedConfig, }), cspHeaders: cspHeaders({api}), ensureInstalledOnShop: ensureInstalled({ api, config: validatedConfig, }), redirectToShopifyOrAppRoot: redirectToShopifyOrAppRoot({ api, config: validatedConfig, }), redirectOutOfApp: redirectOutOfApp({api, config: validatedConfig}), }; } function apiConfigWithDefaults<Params extends Partial<ApiConfigParams>>( apiConfig: Params, ): DefaultedConfigs<Params> { let userAgent = `Shopify Express Library v${SHOPIFY_EXPRESS_LIBRARY_VERSION}`; if (apiConfig?.userAgentPrefix) { userAgent = `${apiConfig.userAgentPrefix} | ${userAgent}`; } /* eslint-disable no-process-env */ return { apiKey: process.env.SHOPIFY_API_KEY!, apiSecretKey: process.env.SHOPIFY_API_SECRET!, scopes: process.env.SCOPES?.split(',')!, hostScheme: (process.env.HOST?.split('://')[0] as 'http' | 'https')!, hostName: process.env.HOST?.replace(/https?:\/\//, '')!, isEmbeddedApp: true, ...(apiConfig || {}), userAgentPrefix: userAgent, } as DefaultedConfigs<Params>; /* eslint-enable no-process-env */ } function validateAppConfig<Params extends Omit<AppConfigParams, 'api'>>( config: Params, api: Shopify, ): ConfigInterfaceFromParams<Params> { const {sessionStorage, ...configWithoutSessionStorage} = config; return { // We override the API package's logger to add the right package context by default (and make the call simpler) logger: overrideLoggerPackage(api.logger), useOnlineTokens: false, exitIframePath: '/exitiframe', sessionStorage: (sessionStorage ?? new MemorySessionStorage()) as ConfigInterfaceFromParams<Params>['sessionStorage'], ...configWithoutSessionStorage, auth: config.auth, webhooks: config.webhooks, }; } function overrideLoggerPackage(logger: Shopify['logger']): Shopify['logger'] { const baseContext = {package: 'shopify-app'}; const warningFunction: Shopify['logger']['warning'] = ( message, context = {}, ) => logger.warning(message, {...baseContext, ...context}); return { ...logger, log: (severity, message, context = {}) => logger.log(severity, message, {...baseContext, ...context}), debug: (message, context = {}) => logger.debug(message, {...baseContext, ...context}), info: (message, context = {}) => logger.info(message, {...baseContext, ...context}), warning: warningFunction, error: (message, context = {}) => logger.error(message, {...baseContext, ...context}), deprecated: deprecated(warningFunction), }; } function deprecated(warningFunction: Shopify['logger']['warning']) { return function (version: string, message: string): Promise<void> { if (compare(SHOPIFY_EXPRESS_LIBRARY_VERSION, version, '>=')) { throw new FeatureDeprecatedError( `Feature was deprecated in version ${version}`, ); } return warningFunction(`[Deprecated | ${version}] ${message}`); }; }