import {RegisterReturn, Shopify} from '@shopify/shopify-api'; import {SessionStorage} from '@shopify/shopify-app-session-storage'; import type {AuthenticateAdmin} from './authenticate/admin/types'; import type {AuthenticateFlow} from './authenticate/flow/types'; import {AuthenticateFulfillmentService} from './authenticate/fulfillment-service/types'; import type {AuthenticatePublic} from './authenticate/public/types'; import type { AuthenticateWebhook, RegisterWebhooksOptions, } from './authenticate/webhooks/types'; import type {AppConfig, AppConfigArg} from './config-types'; import type { ApiConfigWithFutureFlags, ApiFutureFlags, FutureFlagOptions, } from './future/flags'; import type {Unauthenticated} from './unauthenticated/types'; import type {AuthenticatePOS} from './authenticate/pos/types'; export interface BasicParams< Future extends FutureFlagOptions = FutureFlagOptions, > { api: Shopify, any, ApiFutureFlags>; config: AppConfig; logger: Shopify['logger']; } // eslint-disable-next-line no-warning-comments // TODO: Use this enum to replace the isCustomStoreApp config option in shopify-api-js export enum AppDistribution { AppStore = 'app_store', SingleMerchant = 'single_merchant', ShopifyAdmin = 'shopify_admin', } type RegisterWebhooks = ( options: RegisterWebhooksOptions, ) => Promise; export enum LoginErrorType { MissingShop = 'MISSING_SHOP', InvalidShop = 'INVALID_SHOP', } export interface LoginError { shop?: LoginErrorType; } type Login = (request: Request) => Promise; type AddDocumentResponseHeaders = (request: Request, headers: Headers) => void; type SessionStorageType = Config['sessionStorage'] extends SessionStorage ? Config['sessionStorage'] : SessionStorage; interface Authenticate { /** * Authenticate an admin Request and get back an authenticated admin context. Use the authenticated admin context to interact with Shopify. * * Examples of when to use this are requests from your app's UI, or requests from admin extensions. * * If there is no session for the Request, this will redirect the merchant to correct auth flows. * * @example * Authenticating a request for an embedded app. * ```ts * // /app/routes/**\/*.jsx * import { LoaderFunctionArgs, json } from "react-router"; * import { authenticate } from "../../shopify.server"; * * export async function loader({ request }: LoaderFunctionArgs) { * const {admin, session, sessionToken, billing} = authenticate.admin(request); * const response = await admin.graphql(`{ shop { name } }`) * * return (await response.json()); * } * ``` * ```ts * // /app/shopify.server.ts * import { ApiVersion, shopifyApp } from "@shopify/shopify-app-react-router/server"; * * const shopify = shopifyApp({ * // ...etc * }); * export default shopify; * export const authenticate = shopify.authenticate; * ``` */ admin: AuthenticateAdmin; /** * Authenticate a Flow extension Request and get back an authenticated context, containing an admin context to access * the API, and the payload of the request. * * If there is no session for the Request, this will return an HTTP 400 error. * * Note that this will always be a POST request. * * @example * Authenticating a Flow extension request. * ```ts * // /app/routes/**\/*.jsx * import { ActionFunctionArgs, json } from "react-router"; * import { authenticate } from "../../shopify.server"; * * export async function action({ request }: ActionFunctionArgs) { * const {admin, session, payload} = authenticate.flow(request); * * // Perform flow extension logic * * // Return a 200 response * return null; * } * ``` * ```ts * // /app/shopify.server.ts * import { ApiVersion, shopifyApp } from "@shopify/shopify-app-react-router/server"; * * const shopify = shopifyApp({ * // ...etc * }); * export default shopify; * export const authenticate = shopify.authenticate; * ``` */ flow: AuthenticateFlow; /** * Authenticate a request from a fulfillment service and get back an authenticated context. * * @example * Shopify session for the fulfillment service request. * Use the session associated with this request to use the Admin GraphQL API * ```ts * // /app/routes/fulfillment_order_notification.ts * import { ActionFunctionArgs } from "react-router"; * import { authenticate } from "../shopify.server"; * * export async function action({ request }: ActionFunctionArgs) { * const { admin, session } = await authenticate.fulfillmentService(request); * * console.log(session.id) * * return new Response(); * } * ``` * */ fulfillmentService: AuthenticateFulfillmentService; /** * Authenticate a request from a POS UI extension * * @example * Authenticating a POS UI extension request * ```ts * // /app/routes/public/widgets.ts * import { LoaderFunctionArgs, json } from "react-router"; * import { authenticate } from "../shopify.server"; * * export const loader = async ({ request }: LoaderFunctionArgs) => { * const { sessionToken, cors } = await authenticate.pos( * request, * ); * return cors({my: "data", shop: sessionToken.dest})); * }; * ``` */ pos: AuthenticatePOS; /** * Authenticate a public request and get back a session token. * * @example * Authenticating a request from a checkout extension * * ```ts * // /app/routes/api/checkout.jsx * import { LoaderFunctionArgs, json } from "react-router"; * import { authenticate } from "../../shopify.server"; * import { getWidgets } from "~/db/widgets"; * * export async function loader({ request }: LoaderFunctionArgs) { * const {sessionToken} = authenticate.public.checkout(request); * * return (await getWidgets(sessionToken)); * } * ``` */ public: AuthenticatePublic; /** * Authenticate a Shopify webhook request, get back an authenticated admin context and details on the webhook request * * @example * Authenticating a webhook request * * ```ts * // app/routes/webhooks.ts * import { ActionFunctionArgs } from "react-router"; * import { authenticate } from "../shopify.server"; * import db from "../db.server"; * * export const action = async ({ request }: ActionFunctionArgs) => { * const { topic, shop, session, payload } = await authenticate.webhook(request); * * // Webhook requests can trigger after an app is uninstalled * // If the app is already uninstalled, the session may be undefined. * if (!session) { * throw new Response(); * } * * // Handle the webhook * console.log(`${TOPIC} webhook received with payload:`, JSON.stringify(payload)) * * throw new Response(); * }; * ``` * * @example * Registering app-specific webhooks (Recommended) * ```toml * # shopify.app.toml * [webhooks] * api_version = "2024-07" * [[webhooks.subscriptions]] * topics = ["products/create"] * uri = "/webhooks/products/create" * * ``` * * @example * Registering shop-specific webhooks. * In many cases you won't need this. Please see: [https://shopify.dev/docs/apps/build/webhooks/subscribe#app-specific-vs-shop-specific-subscriptions](https://shopify.dev/docs/apps/build/webhooks/subscribe#app-specific-vs-shop-specific-subscriptions) * * ```ts * // app/shopify.server.ts * import { DeliveryMethod, shopifyApp } from "@shopify/shopify-app-react-router/server"; * * const shopify = shopifyApp({ * webhooks: { * PRODUCTS_CREATE: { * deliveryMethod: DeliveryMethod.Http, * callbackUrl: "/webhooks/products/create", * }, * }, * hooks: { * afterAuth: async ({ session }) => { * // Register webhooks for the shop * // In this example, every shop will have these webhooks * // You could wrap this in some custom shop specific conditional logic if needed * shopify.registerWebhooks({ session }); * }, * }, * // ...etc * }); * ``` */ webhook: AuthenticateWebhook; } export interface ShopifyAppBase { /** * The `SessionStorage` instance you passed in as a config option. * * @example * Storing sessions with Prisma. * Import the `@shopify/shopify-app-session-storage-prisma` package to store sessions in your Prisma database. * ```ts * // /app/shopify.server.ts * import { shopifyApp } from "@shopify/shopify-app-react-router/server"; * import { PrismaSessionStorage } from "@shopify/shopify-app-session-storage-prisma"; * import prisma from "~/db.server"; * * const shopify = shopifyApp({ * sessionStorage: new PrismaSessionStorage(prisma), * // ...etc * }) * * // shopify.sessionStorage is an instance of PrismaSessionStorage * ``` */ sessionStorage?: SessionStorageType; /** * Adds the required Content Security Policy headers for Shopify apps to the given Headers object. * * {@link https://shopify.dev/docs/apps/store/security/iframe-protection} * * @example * Return headers on all requests. * Add headers to all HTML requests by calling `shopify.addDocumentResponseHeaders` in `entry.server.tsx`. * * ``` * // ~/shopify.server.ts * import { shopifyApp } from "@shopify/shopify-app-react-router/server"; * * const shopify = shopifyApp({ * // ...etc * }); * export default shopify; * export const addDocumentResponseheaders = shopify.addDocumentResponseheaders; * ``` * * ```ts * // entry.server.tsx * import { addDocumentResponseHeaders } from "~/shopify.server"; * * export default function handleRequest( * request: Request, * responseStatusCode: number, * responseHeaders: Headers, * reactRouterContext: EntryContext * ) { * const markup = renderToString( * * ); * * responseHeaders.set("Content-Type", "text/html"); * addDocumentResponseHeaders(request, responseHeaders); * * return new Response("" + markup, { * status: responseStatusCode, * headers: responseHeaders, * }); * } * ``` */ addDocumentResponseHeaders: AddDocumentResponseHeaders; /** * Register shop-specific webhook subscriptions using the Admin GraphQL API. * * In many cases defining app-specific webhooks in the `shopify.app.toml` will be sufficient and easier to manage. Please see: * * {@link https://shopify.dev/docs/apps/build/webhooks/subscribe#app-specific-vs-shop-specific-subscriptions} * * You should only use this if you need shop-specific webhooks. * * @example * Registering shop-specific webhooks after install * Trigger the registration to create the shop-specific webhook subscriptions after a merchant installs your app using the `afterAuth` hook. Learn more about [subscribing to webhooks.](https://shopify.dev/docs/api/shopify-app-react-router/v3/guide-webhooks) * ```ts * // app/shopify.server.ts * import { DeliveryMethod, shopifyApp } from "@shopify/shopify-app-react-router/server"; * * const shopify = shopifyApp({ * webhooks: { * PRODUCTS_CREATE: { * deliveryMethod: DeliveryMethod.Http, * callbackUrl: "/webhooks/products/create", * }, * }, * hooks: { * afterAuth: async ({ session }) => { * // Register webhooks for the shop * // In this example, every shop will have these webhooks * // You could wrap this in some custom shop specific conditional logic if needed * shopify.registerWebhooks({ session }); * }, * }, * // ...etc * }); * ``` */ registerWebhooks: RegisterWebhooks; /** * Ways to authenticate requests from different surfaces across Shopify. * * @example * Authenticate Shopify requests. * Use the functions in `authenticate` to validate requests coming from Shopify. * ```ts * // /app/shopify.server.ts * import { ApiVersion, shopifyApp } from "@shopify/shopify-app-react-router/server"; * * const shopify = shopifyApp({ * // ...etc * }); * export default shopify; * ``` * ```ts * // /app/routes/**\/*.jsx * import { LoaderFunctionArgs, json } from "react-router"; * import shopify from "../../shopify.server"; * * export async function loader({ request }: LoaderFunctionArgs) { * const {admin, session, sessionToken, billing} = shopify.authenticate.admin(request); * const response = admin.graphql(`{ shop { name } }`) * * return (await response.json()); * } * ``` */ authenticate: Authenticate; /** * Ways to get Contexts from requests that do not originate from Shopify. * * @example * Using unauthenticated contexts. * Create contexts for requests that don't come from Shopify. * ```ts * // /app/shopify.server.ts * import { ApiVersion, shopifyApp } from "@shopify/shopify-app-react-router/server"; * * const shopify = shopifyApp({ * // ...etc * }); * export default shopify; * ``` * ```ts * // /app/routes/**\/*.jsx * import { LoaderFunctionArgs, json } from "react-router"; * import { authenticateExternal } from "~/helpers/authenticate" * import shopify from "../../shopify.server"; * * export async function loader({ request }: LoaderFunctionArgs) { * const shop = await authenticateExternal(request) * const {admin} = await shopify.unauthenticated.admin(shop); * const response = admin.graphql(`{ shop { currencyCode } }`) * * return (await response.json()); * } * ``` */ unauthenticated: Unauthenticated; } export interface ShopifyAppLogin { /** * Log a merchant in, and redirect them to the app root. Will redirect the merchant to authentication if a shop is * present in the URL search parameters or form data. * * This function won't be present when the `distribution` config option is set to `AppDistribution.ShopifyAdmin`, * because Admin apps aren't allowed to show a login page. * * @example * Creating a login page. * Use `shopify.login` to create a login form, in a route that can handle GET and POST requests. * ```ts * // /app/shopify.server.ts * import { ApiVersion, shopifyApp } from "@shopify/shopify-app-react-router/server"; * * const shopify = shopifyApp({ * // ...etc * }); * export default shopify; * ``` * ```ts * // /app/routes/auth/login.tsx * import shopify from "../../shopify.server"; * * export async function loader({ request }: LoaderFunctionArgs) { * const errors = shopify.login(request); * * return (errors); * } * * export async function action({ request }: ActionFunctionArgs) { * const errors = shopify.login(request); * * return (errors); * } * * export default function Auth() { * const actionData = useActionData(); * const [shop, setShop] = useState(""); * * return ( * * *
* * * Login * * * * *
*
*
* ); * } * ``` */ login: Login; } export type AdminApp = ShopifyAppBase; export type SingleMerchantApp = ShopifyAppBase & ShopifyAppLogin; export type AppStoreApp = ShopifyAppBase & ShopifyAppLogin; type EnforceSessionStorage = Base & { sessionStorage: SessionStorageType; }; /** * An object your app can use to interact with Shopify. * * By default, the app's distribution is `AppStore`. */ export type ShopifyApp = Config['distribution'] extends AppDistribution.ShopifyAdmin ? AdminApp : Config['distribution'] extends AppDistribution.SingleMerchant ? EnforceSessionStorage> : Config['distribution'] extends AppDistribution.AppStore ? EnforceSessionStorage> : EnforceSessionStorage>;