@shopify/shopify-app-remix 4.1.1 → 4.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/react/components/AppProvider/AppProvider.js.map +1 -1
- package/dist/cjs/react/components/AppProxyForm/AppProxyForm.js +4 -3
- package/dist/cjs/react/components/AppProxyForm/AppProxyForm.js.map +1 -1
- package/dist/cjs/react/components/AppProxyLink/AppProxyLink.js +4 -3
- package/dist/cjs/react/components/AppProxyLink/AppProxyLink.js.map +1 -1
- package/dist/cjs/react/components/AppProxyProvider/AppProxyProvider.js.map +1 -1
- package/dist/cjs/server/authenticate/admin/helpers/redirect.js +2 -2
- package/dist/cjs/server/authenticate/admin/helpers/redirect.js.map +1 -1
- package/dist/cjs/server/authenticate/admin/strategies/auth-code-flow.js.map +1 -1
- package/dist/cjs/server/authenticate/admin/strategies/merchant-custom-app.js.map +1 -1
- package/dist/cjs/server/authenticate/admin/strategies/token-exchange.js.map +1 -1
- package/dist/cjs/server/authenticate/webhooks/authenticate.js +34 -10
- package/dist/cjs/server/authenticate/webhooks/authenticate.js.map +1 -1
- package/dist/cjs/server/future/flags.js.map +1 -1
- package/dist/cjs/server/shopify-app.js +1 -0
- package/dist/cjs/server/shopify-app.js.map +1 -1
- package/dist/cjs/server/version.js +1 -1
- package/dist/cjs/server/version.js.map +1 -1
- package/dist/esm/react/components/AppProvider/AppProvider.mjs.map +1 -1
- package/dist/esm/react/components/AppProxyForm/AppProxyForm.mjs +5 -4
- package/dist/esm/react/components/AppProxyForm/AppProxyForm.mjs.map +1 -1
- package/dist/esm/react/components/AppProxyLink/AppProxyLink.mjs +5 -4
- package/dist/esm/react/components/AppProxyLink/AppProxyLink.mjs.map +1 -1
- package/dist/esm/react/components/AppProxyProvider/AppProxyProvider.mjs.map +1 -1
- package/dist/esm/server/authenticate/admin/helpers/redirect.mjs +2 -2
- package/dist/esm/server/authenticate/admin/helpers/redirect.mjs.map +1 -1
- package/dist/esm/server/authenticate/admin/strategies/auth-code-flow.mjs.map +1 -1
- package/dist/esm/server/authenticate/admin/strategies/merchant-custom-app.mjs.map +1 -1
- package/dist/esm/server/authenticate/admin/strategies/token-exchange.mjs.map +1 -1
- package/dist/esm/server/authenticate/webhooks/authenticate.mjs +35 -11
- package/dist/esm/server/authenticate/webhooks/authenticate.mjs.map +1 -1
- package/dist/esm/server/future/flags.mjs.map +1 -1
- package/dist/esm/server/shopify-app.mjs +1 -0
- package/dist/esm/server/shopify-app.mjs.map +1 -1
- package/dist/esm/server/version.mjs +1 -1
- package/dist/esm/server/version.mjs.map +1 -1
- package/dist/ts/react/components/AppProvider/AppProvider.d.ts +5 -1
- package/dist/ts/react/components/AppProvider/AppProvider.d.ts.map +1 -1
- package/dist/ts/react/components/AppProxyForm/AppProxyForm.d.ts +2 -1
- package/dist/ts/react/components/AppProxyForm/AppProxyForm.d.ts.map +1 -1
- package/dist/ts/react/components/AppProxyLink/AppProxyLink.d.ts +2 -1
- package/dist/ts/react/components/AppProxyLink/AppProxyLink.d.ts.map +1 -1
- package/dist/ts/react/components/AppProxyProvider/AppProxyProvider.d.ts +5 -1
- package/dist/ts/react/components/AppProxyProvider/AppProxyProvider.d.ts.map +1 -1
- package/dist/ts/server/authenticate/admin/billing/types.d.ts +4 -0
- package/dist/ts/server/authenticate/admin/billing/types.d.ts.map +1 -1
- package/dist/ts/server/authenticate/admin/scope/types.d.ts +2 -1
- package/dist/ts/server/authenticate/admin/scope/types.d.ts.map +1 -1
- package/dist/ts/server/authenticate/admin/strategies/auth-code-flow.d.ts.map +1 -1
- package/dist/ts/server/authenticate/admin/strategies/merchant-custom-app.d.ts.map +1 -1
- package/dist/ts/server/authenticate/admin/strategies/token-exchange.d.ts.map +1 -1
- package/dist/ts/server/authenticate/admin/types.d.ts +10 -0
- package/dist/ts/server/authenticate/admin/types.d.ts.map +1 -1
- package/dist/ts/server/authenticate/flow/types.d.ts +4 -0
- package/dist/ts/server/authenticate/flow/types.d.ts.map +1 -1
- package/dist/ts/server/authenticate/fulfillment-service/types.d.ts +4 -0
- package/dist/ts/server/authenticate/fulfillment-service/types.d.ts.map +1 -1
- package/dist/ts/server/authenticate/public/appProxy/types.d.ts +4 -0
- package/dist/ts/server/authenticate/public/appProxy/types.d.ts.map +1 -1
- package/dist/ts/server/authenticate/public/checkout/types.d.ts +4 -0
- package/dist/ts/server/authenticate/public/checkout/types.d.ts.map +1 -1
- package/dist/ts/server/authenticate/public/customer-account/types.d.ts +4 -0
- package/dist/ts/server/authenticate/public/customer-account/types.d.ts.map +1 -1
- package/dist/ts/server/authenticate/public/extension/types.d.ts.map +1 -1
- package/dist/ts/server/authenticate/webhooks/authenticate.d.ts.map +1 -1
- package/dist/ts/server/authenticate/webhooks/types.d.ts +36 -3
- package/dist/ts/server/authenticate/webhooks/types.d.ts.map +1 -1
- package/dist/ts/server/boundary/error.d.ts +1 -1
- package/dist/ts/server/boundary/error.d.ts.map +1 -1
- package/dist/ts/server/clients/admin/types.d.ts +4 -0
- package/dist/ts/server/clients/admin/types.d.ts.map +1 -1
- package/dist/ts/server/clients/storefront/types.d.ts +4 -0
- package/dist/ts/server/clients/storefront/types.d.ts.map +1 -1
- package/dist/ts/server/config-types.d.ts.map +1 -1
- package/dist/ts/server/future/flags.d.ts +6 -0
- package/dist/ts/server/future/flags.d.ts.map +1 -1
- package/dist/ts/server/shopify-app.d.ts +1 -0
- package/dist/ts/server/shopify-app.d.ts.map +1 -1
- package/dist/ts/server/unauthenticated/admin/types.d.ts +4 -0
- package/dist/ts/server/unauthenticated/admin/types.d.ts.map +1 -1
- package/dist/ts/server/unauthenticated/storefront/types.d.ts +4 -0
- package/dist/ts/server/unauthenticated/storefront/types.d.ts.map +1 -1
- package/dist/ts/server/version.d.ts +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +19 -15
- package/src/react/components/AppProvider/AppProvider.tsx +151 -0
- package/src/react/components/AppProvider/index.ts +1 -0
- package/src/react/components/AppProxyForm/AppProxyForm.tsx +85 -0
- package/src/react/components/AppProxyForm/index.ts +1 -0
- package/src/react/components/AppProxyLink/AppProxyLink.tsx +61 -0
- package/src/react/components/AppProxyLink/index.ts +1 -0
- package/src/react/components/AppProxyProvider/AppProxyProvider.tsx +104 -0
- package/src/react/components/AppProxyProvider/index.ts +5 -0
- package/src/react/components/RemixPolarisLink.tsx +14 -0
- package/src/react/components/index.ts +4 -0
- package/src/react/const.ts +2 -0
- package/src/react/index.ts +1 -0
- package/src/server/__test-helpers/const.ts +14 -0
- package/src/server/__test-helpers/expect-admin-api-client.ts +51 -0
- package/src/server/__test-helpers/expect-begin-auth-redirect.ts +22 -0
- package/src/server/__test-helpers/expect-document-request-headers.ts +25 -0
- package/src/server/__test-helpers/expect-exit-iframe.ts +37 -0
- package/src/server/__test-helpers/expect-login-redirect.ts +8 -0
- package/src/server/__test-helpers/expect-storefront-api-client.ts +49 -0
- package/src/server/__test-helpers/expect-token-refresh.ts +188 -0
- package/src/server/__test-helpers/get-hmac.ts +7 -0
- package/src/server/__test-helpers/get-jwt.ts +10 -0
- package/src/server/__test-helpers/get-thrown-error.ts +19 -0
- package/src/server/__test-helpers/get-thrown-response.ts +17 -0
- package/src/server/__test-helpers/index.ts +21 -0
- package/src/server/__test-helpers/mock-graphql-request.ts +60 -0
- package/src/server/__test-helpers/request-mock.ts +169 -0
- package/src/server/__test-helpers/setup-embedded-flow.ts +30 -0
- package/src/server/__test-helpers/setup-fetch-flow.ts +29 -0
- package/src/server/__test-helpers/setup-non-embedded-flow.ts +31 -0
- package/src/server/__test-helpers/setup-valid-request.ts +78 -0
- package/src/server/__test-helpers/setup-valid-session.ts +28 -0
- package/src/server/__test-helpers/sign-request-cookie.ts +32 -0
- package/src/server/__test-helpers/test-config.ts +56 -0
- package/src/server/adapters/node/index.ts +13 -0
- package/src/server/adapters/vercel/index.ts +13 -0
- package/src/server/authenticate/admin/authenticate.admin.doc.example.ts +34 -0
- package/src/server/authenticate/admin/authenticate.ts +232 -0
- package/src/server/authenticate/admin/billing/cancel.ts +38 -0
- package/src/server/authenticate/admin/billing/check.ts +41 -0
- package/src/server/authenticate/admin/billing/create-usage-record.ts +36 -0
- package/src/server/authenticate/admin/billing/helpers.ts +42 -0
- package/src/server/authenticate/admin/billing/index.ts +6 -0
- package/src/server/authenticate/admin/billing/request.ts +64 -0
- package/src/server/authenticate/admin/billing/require.ts +58 -0
- package/src/server/authenticate/admin/billing/types.ts +617 -0
- package/src/server/authenticate/admin/billing/update-usage-subscription-capped-amount.ts +55 -0
- package/src/server/authenticate/admin/helpers/begin-auth.ts +17 -0
- package/src/server/authenticate/admin/helpers/create-admin-api-context.ts +20 -0
- package/src/server/authenticate/admin/helpers/ensure-app-is-embedded-if-required.ts +18 -0
- package/src/server/authenticate/admin/helpers/ensure-session-token-search-param-if-required.ts +25 -0
- package/src/server/authenticate/admin/helpers/handle-client-error.ts +45 -0
- package/src/server/authenticate/admin/helpers/index.ts +14 -0
- package/src/server/authenticate/admin/helpers/redirect-to-auth-page.ts +28 -0
- package/src/server/authenticate/admin/helpers/redirect-to-bounce-page.ts +23 -0
- package/src/server/authenticate/admin/helpers/redirect-to-install-page.ts +53 -0
- package/src/server/authenticate/admin/helpers/redirect-to-shopify-or-app-root.ts +21 -0
- package/src/server/authenticate/admin/helpers/redirect-with-app-bridge-headers.ts +13 -0
- package/src/server/authenticate/admin/helpers/redirect-with-exitiframe.ts +28 -0
- package/src/server/authenticate/admin/helpers/redirect.ts +164 -0
- package/src/server/authenticate/admin/helpers/render-app-bridge.ts +46 -0
- package/src/server/authenticate/admin/helpers/trigger-after-auth-hook.ts +29 -0
- package/src/server/authenticate/admin/helpers/validate-redirect-url.ts +67 -0
- package/src/server/authenticate/admin/helpers/validate-shop-and-host-params.ts +46 -0
- package/src/server/authenticate/admin/scope/client/fetch-scopes-details.ts +43 -0
- package/src/server/authenticate/admin/scope/client/revoke-scopes.ts +38 -0
- package/src/server/authenticate/admin/scope/factory.ts +21 -0
- package/src/server/authenticate/admin/scope/query.ts +51 -0
- package/src/server/authenticate/admin/scope/request.ts +32 -0
- package/src/server/authenticate/admin/scope/revoke.ts +48 -0
- package/src/server/authenticate/admin/scope/types.ts +127 -0
- package/src/server/authenticate/admin/strategies/auth-code-flow.ts +332 -0
- package/src/server/authenticate/admin/strategies/merchant-custom-app.ts +65 -0
- package/src/server/authenticate/admin/strategies/token-exchange.ts +188 -0
- package/src/server/authenticate/admin/strategies/types.ts +29 -0
- package/src/server/authenticate/admin/types.ts +226 -0
- package/src/server/authenticate/const.ts +15 -0
- package/src/server/authenticate/flow/authenticate.flow.doc.example.ts +39 -0
- package/src/server/authenticate/flow/authenticate.ts +68 -0
- package/src/server/authenticate/flow/types.ts +89 -0
- package/src/server/authenticate/fulfillment-service/authenticate.fulfillment-service.doc.example.ts +52 -0
- package/src/server/authenticate/fulfillment-service/authenticate.ts +82 -0
- package/src/server/authenticate/fulfillment-service/types.ts +86 -0
- package/src/server/authenticate/helpers/add-response-headers.ts +43 -0
- package/src/server/authenticate/helpers/app-bridge-url.ts +10 -0
- package/src/server/authenticate/helpers/ensure-cors-headers.ts +38 -0
- package/src/server/authenticate/helpers/get-session-token-header.ts +11 -0
- package/src/server/authenticate/helpers/get-shop-from-request.ts +4 -0
- package/src/server/authenticate/helpers/idempotent-promise-handler.ts +45 -0
- package/src/server/authenticate/helpers/index.ts +10 -0
- package/src/server/authenticate/helpers/invalidate-access-token.ts +17 -0
- package/src/server/authenticate/helpers/reject-bot-request.ts +27 -0
- package/src/server/authenticate/helpers/respond-to-invalid-session-token.ts +28 -0
- package/src/server/authenticate/helpers/respond-to-options-request.ts +26 -0
- package/src/server/authenticate/helpers/validate-session-token.ts +40 -0
- package/src/server/authenticate/login/login.ts +53 -0
- package/src/server/authenticate/public/appProxy/authenticate.public.app-proxy.doc.example.ts +27 -0
- package/src/server/authenticate/public/appProxy/authenticate.ts +150 -0
- package/src/server/authenticate/public/appProxy/types.ts +219 -0
- package/src/server/authenticate/public/checkout/authenticate.public.checkout.doc.example.offers.ts +21 -0
- package/src/server/authenticate/public/checkout/authenticate.public.checkout.doc.example.ts +17 -0
- package/src/server/authenticate/public/checkout/authenticate.ts +13 -0
- package/src/server/authenticate/public/checkout/types.ts +69 -0
- package/src/server/authenticate/public/customer-account/authenticate.public.customerAccount.doc.example.offers.ts +36 -0
- package/src/server/authenticate/public/customer-account/authenticate.public.customerAccount.doc.example.ts +18 -0
- package/src/server/authenticate/public/customer-account/authenticate.ts +13 -0
- package/src/server/authenticate/public/customer-account/types.ts +69 -0
- package/src/server/authenticate/public/extension/authenticate.ts +54 -0
- package/src/server/authenticate/public/extension/types.ts +11 -0
- package/src/server/authenticate/public/factory.ts +24 -0
- package/src/server/authenticate/public/index.ts +1 -0
- package/src/server/authenticate/public/pos/authenticate.ts +8 -0
- package/src/server/authenticate/public/pos/types.ts +65 -0
- package/src/server/authenticate/public/types.ts +96 -0
- package/src/server/authenticate/webhooks/authenticate.ts +105 -0
- package/src/server/authenticate/webhooks/authenticate.webhooks.doc.example.ts +43 -0
- package/src/server/authenticate/webhooks/index.ts +1 -0
- package/src/server/authenticate/webhooks/register.ts +48 -0
- package/src/server/authenticate/webhooks/types.ts +267 -0
- package/src/server/boundary/error.tsx +14 -0
- package/src/server/boundary/headers.ts +15 -0
- package/src/server/boundary/index.ts +39 -0
- package/src/server/boundary/types.ts +4 -0
- package/src/server/clients/admin/factory.ts +22 -0
- package/src/server/clients/admin/graphql.ts +39 -0
- package/src/server/clients/admin/index.ts +2 -0
- package/src/server/clients/admin/types.ts +124 -0
- package/src/server/clients/index.ts +2 -0
- package/src/server/clients/storefront/factory.ts +32 -0
- package/src/server/clients/storefront/index.ts +2 -0
- package/src/server/clients/storefront/types.ts +82 -0
- package/src/server/clients/types.ts +47 -0
- package/src/server/config-types.ts +309 -0
- package/src/server/errors.ts +3 -0
- package/src/server/future/flags.ts +68 -0
- package/src/server/helpers/create-or-load-offline-session.ts +19 -0
- package/src/server/helpers/ensure-offline-token-is-not-expired.ts +32 -0
- package/src/server/helpers/ensure-valid-offline-session.ts +15 -0
- package/src/server/helpers/index.ts +4 -0
- package/src/server/helpers/refresh-token.ts +35 -0
- package/src/server/index.ts +24 -0
- package/src/server/override-logger.ts +42 -0
- package/src/server/shopify-app.ts +216 -0
- package/src/server/test-helpers/index.ts +1 -0
- package/src/server/test-helpers/test-config.ts +79 -0
- package/src/server/types-contexts.ts +44 -0
- package/src/server/types.ts +507 -0
- package/src/server/unauthenticated/admin/factory.ts +23 -0
- package/src/server/unauthenticated/admin/index.ts +1 -0
- package/src/server/unauthenticated/admin/types.ts +83 -0
- package/src/server/unauthenticated/storefront/factory.ts +28 -0
- package/src/server/unauthenticated/storefront/index.ts +1 -0
- package/src/server/unauthenticated/storefront/types.ts +109 -0
- package/src/server/unauthenticated/types.ts +65 -0
- package/src/server/version.ts +1 -0
- package/dist/ts/react/components/AppProvider/AppProvider.doc.d.ts +0 -4
- package/dist/ts/react/components/AppProvider/AppProvider.doc.d.ts.map +0 -1
- package/dist/ts/react/components/AppProxyForm/AppProxyForm.doc.d.ts +0 -4
- package/dist/ts/react/components/AppProxyForm/AppProxyForm.doc.d.ts.map +0 -1
- package/dist/ts/react/components/AppProxyLink/AppProxyLink.doc.d.ts +0 -4
- package/dist/ts/react/components/AppProxyLink/AppProxyLink.doc.d.ts.map +0 -1
- package/dist/ts/react/components/AppProxyProvider/AppProxyProvider.doc.d.ts +0 -4
- package/dist/ts/react/components/AppProxyProvider/AppProxyProvider.doc.d.ts.map +0 -1
- package/dist/ts/server/authenticate/admin/authenticate.admin.doc.d.ts +0 -4
- package/dist/ts/server/authenticate/admin/authenticate.admin.doc.d.ts.map +0 -1
- package/dist/ts/server/authenticate/admin/billing/authenticate.admin.billing.doc.d.ts +0 -4
- package/dist/ts/server/authenticate/admin/billing/authenticate.admin.billing.doc.d.ts.map +0 -1
- package/dist/ts/server/authenticate/admin/scope/authenticate.admin.scopes.doc.d.ts +0 -4
- package/dist/ts/server/authenticate/admin/scope/authenticate.admin.scopes.doc.d.ts.map +0 -1
- package/dist/ts/server/authenticate/flow/authenticate.flow.doc.d.ts +0 -4
- package/dist/ts/server/authenticate/flow/authenticate.flow.doc.d.ts.map +0 -1
- package/dist/ts/server/authenticate/fulfillment-service/authenticate.fulfillment-service.doc.d.ts +0 -4
- package/dist/ts/server/authenticate/fulfillment-service/authenticate.fulfillment-service.doc.d.ts.map +0 -1
- package/dist/ts/server/authenticate/public/appProxy/authenticate.public.app-proxy.doc.d.ts +0 -4
- package/dist/ts/server/authenticate/public/appProxy/authenticate.public.app-proxy.doc.d.ts.map +0 -1
- package/dist/ts/server/authenticate/public/checkout/authenticate.public.checkout.doc.d.ts +0 -4
- package/dist/ts/server/authenticate/public/checkout/authenticate.public.checkout.doc.d.ts.map +0 -1
- package/dist/ts/server/authenticate/public/customer-account/authenticate.public.customerAccount.doc.d.ts +0 -4
- package/dist/ts/server/authenticate/public/customer-account/authenticate.public.customerAccount.doc.d.ts.map +0 -1
- package/dist/ts/server/authenticate/webhooks/authenticate.webhooks.doc.d.ts +0 -4
- package/dist/ts/server/authenticate/webhooks/authenticate.webhooks.doc.d.ts.map +0 -1
- package/dist/ts/server/clients/admin/authenticate.admin.api.doc.d.ts +0 -4
- package/dist/ts/server/clients/admin/authenticate.admin.api.doc.d.ts.map +0 -1
- package/dist/ts/server/clients/storefront/authenticate.storefront.api.doc.d.ts +0 -4
- package/dist/ts/server/clients/storefront/authenticate.storefront.api.doc.d.ts.map +0 -1
- package/dist/ts/server/shopify-app.doc.d.ts +0 -4
- package/dist/ts/server/shopify-app.doc.d.ts.map +0 -1
- package/dist/ts/server/unauthenticated/admin/unauthenticated.admin.doc.d.ts +0 -4
- package/dist/ts/server/unauthenticated/admin/unauthenticated.admin.doc.d.ts.map +0 -1
- package/dist/ts/server/unauthenticated/storefront/unauthenticated.storefront.doc.d.ts +0 -4
- package/dist/ts/server/unauthenticated/storefront/unauthenticated.storefront.doc.d.ts.map +0 -1
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import {Session, WebhookTypeValue} from '@shopify/shopify-api';
|
|
2
|
+
|
|
3
|
+
import type {AdminApiContext} from '../../clients';
|
|
4
|
+
|
|
5
|
+
export interface RegisterWebhooksOptions {
|
|
6
|
+
/**
|
|
7
|
+
* The Shopify session used to register webhooks using the Admin API.
|
|
8
|
+
*/
|
|
9
|
+
session: Session;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface Context<Topics = string | number | symbol> {
|
|
13
|
+
/**
|
|
14
|
+
* The API version used for the webhook.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* <caption>Webhook API version.</caption>
|
|
18
|
+
* <description>Get the API version used for webhook request.</description>
|
|
19
|
+
* ```ts
|
|
20
|
+
* // /app/routes/webhooks.tsx
|
|
21
|
+
* import { ActionFunctionArgs } from "@remix-run/node";
|
|
22
|
+
* import { authenticate } from "../shopify.server";
|
|
23
|
+
*
|
|
24
|
+
* export const action = async ({ request }: ActionFunctionArgs) => {
|
|
25
|
+
* const { apiVersion } = await authenticate.webhook(request);
|
|
26
|
+
* return new Response();
|
|
27
|
+
* };
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
apiVersion: string;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The shop where the webhook was triggered.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* <caption>Webhook shop.</caption>
|
|
37
|
+
* <description>Get the shop that triggered a webhook.</description>
|
|
38
|
+
* ```ts
|
|
39
|
+
* // /app/routes/webhooks.tsx
|
|
40
|
+
* import { ActionFunctionArgs } from "@remix-run/node";
|
|
41
|
+
* import { authenticate } from "../shopify.server";
|
|
42
|
+
*
|
|
43
|
+
* export const action = async ({ request }: ActionFunctionArgs) => {
|
|
44
|
+
* const { shop } = await authenticate.webhook(request);
|
|
45
|
+
* return new Response();
|
|
46
|
+
* };
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
shop: string;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The topic of the webhook.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* <caption>Webhook topic.</caption>
|
|
56
|
+
* <description>Get the event topic for the webhook.</description>
|
|
57
|
+
* ```ts
|
|
58
|
+
* // /app/routes/webhooks.tsx
|
|
59
|
+
* import { ActionFunctionArgs } from "@remix-run/node";
|
|
60
|
+
* import { authenticate } from "../shopify.server";
|
|
61
|
+
*
|
|
62
|
+
* export const action = async ({ request }: ActionFunctionArgs) => {
|
|
63
|
+
* const { topic } = await authenticate.webhook(request);
|
|
64
|
+
*
|
|
65
|
+
* switch (topic) {
|
|
66
|
+
* case "APP_UNINSTALLED":
|
|
67
|
+
* // Do something when the app is uninstalled.
|
|
68
|
+
* break;
|
|
69
|
+
* }
|
|
70
|
+
*
|
|
71
|
+
* return new Response();
|
|
72
|
+
* };
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
topic: Topics;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* A unique ID for the webhook. This is the idempotency key — useful to keep track of which
|
|
79
|
+
* events your app has already processed.
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* <caption>Webhook ID.</caption>
|
|
83
|
+
* <description>Get the webhook ID.</description>
|
|
84
|
+
* ```ts
|
|
85
|
+
* // /app/routes/webhooks.tsx
|
|
86
|
+
* import { ActionFunctionArgs } from "@remix-run/node";
|
|
87
|
+
* import { authenticate } from "../shopify.server";
|
|
88
|
+
*
|
|
89
|
+
* export const action = async ({ request }: ActionFunctionArgs) => {
|
|
90
|
+
* const { webhookId } = await authenticate.webhook(request);
|
|
91
|
+
* return new Response();
|
|
92
|
+
* };
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
webhookId: string;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* The payload from the webhook request.
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* <caption>Webhook payload.</caption>
|
|
102
|
+
* <description>Get the request's POST payload.</description>
|
|
103
|
+
* ```ts
|
|
104
|
+
* // /app/routes/webhooks.tsx
|
|
105
|
+
* import { ActionFunctionArgs } from "@remix-run/node";
|
|
106
|
+
* import { authenticate } from "../shopify.server";
|
|
107
|
+
*
|
|
108
|
+
* export const action = async ({ request }: ActionFunctionArgs) => {
|
|
109
|
+
* const { payload } = await authenticate.webhook(request);
|
|
110
|
+
* return new Response();
|
|
111
|
+
* };
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
114
|
+
payload: Record<string, any>;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* The type of webhook: 'webhooks' for traditional webhooks or 'events' for events webhooks.
|
|
118
|
+
*/
|
|
119
|
+
webhookType: WebhookTypeValue;
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* The sub-topic of the webhook. Only available for traditional webhooks.
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* <caption>Webhook sub-topic.</caption>
|
|
126
|
+
* <description>Get the webhook sub-topic.</description>
|
|
127
|
+
* ```ts
|
|
128
|
+
* // /app/routes/webhooks.tsx
|
|
129
|
+
* import { ActionFunctionArgs } from "@remix-run/node";
|
|
130
|
+
* import { authenticate } from "../shopify.server";
|
|
131
|
+
*
|
|
132
|
+
* export const action = async ({ request }: ActionFunctionArgs) => {
|
|
133
|
+
* const { subTopic } = await authenticate.webhook(request);
|
|
134
|
+
* return new Response();
|
|
135
|
+
* };
|
|
136
|
+
* ```
|
|
137
|
+
*
|
|
138
|
+
*/
|
|
139
|
+
subTopic?: string;
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* The name assigned to the webhook subscription. Only available for traditional webhooks.
|
|
143
|
+
*/
|
|
144
|
+
name?: string;
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* The handle for the webhook subscription. Only available for events webhooks.
|
|
148
|
+
*/
|
|
149
|
+
handle?: string;
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* The action type: 'create', 'update', or 'delete'. Only available for events webhooks.
|
|
153
|
+
*/
|
|
154
|
+
action?: string;
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* The GID of the resource that triggered the webhook. Only available for events webhooks.
|
|
158
|
+
*/
|
|
159
|
+
resourceId?: string;
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* The timestamp when the webhook was triggered.
|
|
163
|
+
*/
|
|
164
|
+
triggeredAt?: string;
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* The unique event identifier.
|
|
168
|
+
*/
|
|
169
|
+
eventId?: string;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export interface WebhookContextWithoutSession<
|
|
173
|
+
Topics = string | number | symbol,
|
|
174
|
+
> extends Context<Topics> {
|
|
175
|
+
session: undefined;
|
|
176
|
+
admin: undefined;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export interface WebhookContextWithSession<
|
|
180
|
+
Topics = string | number | symbol,
|
|
181
|
+
> extends Context<Topics> {
|
|
182
|
+
/**
|
|
183
|
+
* A session with an offline token for the shop.
|
|
184
|
+
*
|
|
185
|
+
* Returned only if there is a session for the shop.
|
|
186
|
+
* Webhook requests can trigger after an app is uninstalled
|
|
187
|
+
* If the app is already uninstalled, the session may be undefined.
|
|
188
|
+
* Therefore, you should check for the session before using it.
|
|
189
|
+
*
|
|
190
|
+
* @example
|
|
191
|
+
* <caption>Protecting against uninstalled apps.</caption>
|
|
192
|
+
* ```ts
|
|
193
|
+
* // /app/routes/webhooks.tsx
|
|
194
|
+
* import type { ActionFunctionArgs } from "@remix-run/node";
|
|
195
|
+
* import { authenticate } from "~/shopify.server";
|
|
196
|
+
|
|
197
|
+
* export const action = async ({ request }: ActionFunctionArgs) => {
|
|
198
|
+
* const { session } = await authenticate.webhook(request);
|
|
199
|
+
*
|
|
200
|
+
* // Webhook requests can trigger after an app is uninstalled
|
|
201
|
+
* // If the app is already uninstalled, the session may be undefined.
|
|
202
|
+
* if (!session) {
|
|
203
|
+
* throw new Response();
|
|
204
|
+
* }
|
|
205
|
+
*
|
|
206
|
+
* // Handle webhook request
|
|
207
|
+
* console.log("Received webhook webhook");
|
|
208
|
+
*
|
|
209
|
+
* return new Response();
|
|
210
|
+
* };
|
|
211
|
+
* ```
|
|
212
|
+
*/
|
|
213
|
+
session: Session;
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* An admin context for the webhook.
|
|
217
|
+
*
|
|
218
|
+
* Returned only if there is a session for the shop.
|
|
219
|
+
*
|
|
220
|
+
* @example
|
|
221
|
+
* <caption>Webhook admin context.</caption>
|
|
222
|
+
* <description>Use the `admin` object in the context to interact with the Admin API.</description>
|
|
223
|
+
* ```ts
|
|
224
|
+
* // /app/routes/webhooks.tsx
|
|
225
|
+
* import { ActionFunctionArgs } from "@remix-run/node";
|
|
226
|
+
* import { authenticate } from "../shopify.server";
|
|
227
|
+
*
|
|
228
|
+
* export async function action({ request }: ActionFunctionArgs) {
|
|
229
|
+
* const { admin } = await authenticate.webhook(request);
|
|
230
|
+
*
|
|
231
|
+
* // Webhook requests can trigger after an app is uninstalled
|
|
232
|
+
* // If the app is already uninstalled, the session may be undefined.
|
|
233
|
+
* if (!session) {
|
|
234
|
+
* throw new Response();
|
|
235
|
+
* }
|
|
236
|
+
*
|
|
237
|
+
* const response = await admin?.graphql(
|
|
238
|
+
* `#graphql
|
|
239
|
+
* mutation populateProduct($input: ProductInput!) {
|
|
240
|
+
* productCreate(input: $input) {
|
|
241
|
+
* product {
|
|
242
|
+
* id
|
|
243
|
+
* }
|
|
244
|
+
* }
|
|
245
|
+
* }`,
|
|
246
|
+
* { variables: { input: { title: "Product Name" } } }
|
|
247
|
+
* );
|
|
248
|
+
*
|
|
249
|
+
* const productData = await response.json();
|
|
250
|
+
* return json({ data: productData.data });
|
|
251
|
+
* }
|
|
252
|
+
* ```
|
|
253
|
+
*/
|
|
254
|
+
admin: AdminApiContext;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export type WebhookContext<Topics = string | number | symbol> =
|
|
258
|
+
| WebhookContextWithoutSession<Topics>
|
|
259
|
+
| WebhookContextWithSession<Topics>;
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Verifies requests coming from Shopify webhooks.
|
|
263
|
+
* @publicDocs
|
|
264
|
+
*/
|
|
265
|
+
export type AuthenticateWebhook<Topics = string | number | symbol> = (
|
|
266
|
+
request: Request,
|
|
267
|
+
) => Promise<WebhookContext<Topics>>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function errorBoundary(error: any) {
|
|
2
|
+
if (
|
|
3
|
+
error.constructor.name === 'ErrorResponse' ||
|
|
4
|
+
error.constructor.name === 'ErrorResponseImpl'
|
|
5
|
+
) {
|
|
6
|
+
return (
|
|
7
|
+
<div
|
|
8
|
+
dangerouslySetInnerHTML={{__html: error.data || 'Handling response'}}
|
|
9
|
+
/>
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
throw error;
|
|
14
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import {HeadersArgs} from '@remix-run/server-runtime';
|
|
2
|
+
|
|
3
|
+
export function headersBoundary(headers: HeadersArgs): Headers {
|
|
4
|
+
const {parentHeaders, loaderHeaders, actionHeaders, errorHeaders} = headers;
|
|
5
|
+
|
|
6
|
+
if (errorHeaders && Array.from(errorHeaders.entries()).length > 0) {
|
|
7
|
+
return errorHeaders;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
return new Headers([
|
|
11
|
+
...(parentHeaders ? Array.from(parentHeaders.entries()) : []),
|
|
12
|
+
...(loaderHeaders ? Array.from(loaderHeaders.entries()) : []),
|
|
13
|
+
...(actionHeaders ? Array.from(actionHeaders.entries()) : []),
|
|
14
|
+
]);
|
|
15
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import {headersBoundary} from './headers';
|
|
2
|
+
import {errorBoundary} from './error';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A collection of functions that handle the necessary code for error boundaries in routes using authenticate.admin.
|
|
6
|
+
*/
|
|
7
|
+
export const boundary = {
|
|
8
|
+
/**
|
|
9
|
+
* A function that handles errors or thrown responses.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* <caption>Catching errors in a route</caption>
|
|
13
|
+
* ```ts
|
|
14
|
+
* // /app/routes/admin/widgets.ts
|
|
15
|
+
* import { boundary } from "@shopify/shopify-app-remix/server";
|
|
16
|
+
*
|
|
17
|
+
* export function ErrorBoundary() {
|
|
18
|
+
* return boundary.error(useRouteError());
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
error: errorBoundary,
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* A function that sets the appropriate document response headers.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* <caption>Catching errors in a route</caption>
|
|
29
|
+
* ```ts
|
|
30
|
+
* // /app/routes/admin/widgets.ts
|
|
31
|
+
* import { boundary } from "@shopify/shopify-app-remix/server";
|
|
32
|
+
*
|
|
33
|
+
* export const headers = (headersArgs) => {
|
|
34
|
+
* return boundary.headers(headersArgs);
|
|
35
|
+
* };
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
headers: headersBoundary,
|
|
39
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {Session} from '@shopify/shopify-api';
|
|
2
|
+
|
|
3
|
+
import {BasicParams} from '../../types';
|
|
4
|
+
|
|
5
|
+
import {graphqlClientFactory} from './graphql';
|
|
6
|
+
import type {AdminApiContext} from './types';
|
|
7
|
+
|
|
8
|
+
interface AdminClientOptions {
|
|
9
|
+
params: BasicParams;
|
|
10
|
+
session: Session;
|
|
11
|
+
handleClientError?: (error: any) => Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function adminClientFactory({
|
|
15
|
+
params,
|
|
16
|
+
handleClientError,
|
|
17
|
+
session,
|
|
18
|
+
}: AdminClientOptions): AdminApiContext {
|
|
19
|
+
return {
|
|
20
|
+
graphql: graphqlClientFactory({params, session, handleClientError}),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import {AdminOperations} from '@shopify/admin-api-client';
|
|
2
|
+
|
|
3
|
+
import {GraphQLClient} from '../types';
|
|
4
|
+
|
|
5
|
+
import {AdminClientOptions} from './types';
|
|
6
|
+
|
|
7
|
+
// eslint-disable-next-line no-warning-comments
|
|
8
|
+
// TODO: This is actually just a call through to the Shopify API client, but with a different API. We should eventually
|
|
9
|
+
// move this over to the library layer.
|
|
10
|
+
export function graphqlClientFactory({
|
|
11
|
+
params,
|
|
12
|
+
handleClientError,
|
|
13
|
+
session,
|
|
14
|
+
}: AdminClientOptions): GraphQLClient<AdminOperations> {
|
|
15
|
+
return async function query(operation, options) {
|
|
16
|
+
const client = new params.api.clients.Graphql({
|
|
17
|
+
session,
|
|
18
|
+
apiVersion: options?.apiVersion,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
// We convert the incoming response to a Response object to bring this client closer to the Remix client.
|
|
23
|
+
const apiResponse = await client.request(operation, {
|
|
24
|
+
variables: options?.variables,
|
|
25
|
+
retries: options?.tries ? options.tries - 1 : 0,
|
|
26
|
+
headers: options?.headers,
|
|
27
|
+
signal: options?.signal,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
return new Response(JSON.stringify(apiResponse));
|
|
31
|
+
} catch (error) {
|
|
32
|
+
if (handleClientError) {
|
|
33
|
+
throw await handleClientError({error, params, session});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import {AdminOperations} from '@shopify/admin-api-client';
|
|
2
|
+
import {Session} from '@shopify/shopify-api';
|
|
3
|
+
|
|
4
|
+
import {BasicParams} from '../../types';
|
|
5
|
+
import {GraphQLClient} from '../types';
|
|
6
|
+
|
|
7
|
+
export interface AdminClientOptions {
|
|
8
|
+
params: BasicParams;
|
|
9
|
+
session: Session;
|
|
10
|
+
handleClientError?: HandleAdminClientError;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type HandleAdminClientError = (arg: {
|
|
14
|
+
error: any;
|
|
15
|
+
params: BasicParams;
|
|
16
|
+
session: Session;
|
|
17
|
+
}) => Promise<void>;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Provides utilities that apps can use to make requests to the Admin API.
|
|
21
|
+
* @publicDocs
|
|
22
|
+
*/
|
|
23
|
+
export interface AdminApiContext {
|
|
24
|
+
/**
|
|
25
|
+
* Methods for interacting with the Shopify Admin GraphQL API
|
|
26
|
+
*
|
|
27
|
+
* {@link https://shopify.dev/docs/api/admin-graphql}
|
|
28
|
+
* {@link https://github.com/Shopify/shopify-app-js/blob/main/packages/apps/shopify-api/docs/reference/clients/Graphql.md}
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* <caption>Querying the GraphQL API.</caption>
|
|
32
|
+
* <description>Use `admin.graphql` to make query / mutation requests.</description>
|
|
33
|
+
* ```ts
|
|
34
|
+
* // /app/routes/**\/*.ts
|
|
35
|
+
* import { ActionFunctionArgs } from "@remix-run/node";
|
|
36
|
+
* import { authenticate } from "../shopify.server";
|
|
37
|
+
*
|
|
38
|
+
* export const action = async ({ request }: ActionFunctionArgs) => {
|
|
39
|
+
* const { admin } = await authenticate.admin(request);
|
|
40
|
+
*
|
|
41
|
+
* const response = await admin.graphql(
|
|
42
|
+
* `#graphql
|
|
43
|
+
* mutation populateProduct($input: ProductInput!) {
|
|
44
|
+
* productCreate(input: $input) {
|
|
45
|
+
* product {
|
|
46
|
+
* id
|
|
47
|
+
* }
|
|
48
|
+
* }
|
|
49
|
+
* }`,
|
|
50
|
+
* {
|
|
51
|
+
* variables: {
|
|
52
|
+
* input: { title: "Product Name" },
|
|
53
|
+
* },
|
|
54
|
+
* },
|
|
55
|
+
* );
|
|
56
|
+
*
|
|
57
|
+
* const productData = await response.json();
|
|
58
|
+
* return json({
|
|
59
|
+
* productId: productData.data?.productCreate?.product?.id,
|
|
60
|
+
* });
|
|
61
|
+
* }
|
|
62
|
+
* ```
|
|
63
|
+
*
|
|
64
|
+
* ```ts
|
|
65
|
+
* // /app/shopify.server.ts
|
|
66
|
+
* import { shopifyApp } from "@shopify/shopify-app-remix/server";
|
|
67
|
+
*
|
|
68
|
+
* const shopify = shopifyApp({
|
|
69
|
+
* // ...
|
|
70
|
+
* });
|
|
71
|
+
* export default shopify;
|
|
72
|
+
* export const authenticate = shopify.authenticate;
|
|
73
|
+
* ```
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* <caption>Handling GraphQL errors.</caption>
|
|
77
|
+
* <description>Catch `GraphqlQueryError` errors to see error messages from the API.</description>
|
|
78
|
+
* ```ts
|
|
79
|
+
* // /app/routes/**\/*.ts
|
|
80
|
+
* import { ActionFunctionArgs } from "@remix-run/node";
|
|
81
|
+
* import { authenticate } from "../shopify.server";
|
|
82
|
+
*
|
|
83
|
+
* export const action = async ({ request }: ActionFunctionArgs) => {
|
|
84
|
+
* const { admin } = await authenticate.admin(request);
|
|
85
|
+
*
|
|
86
|
+
* try {
|
|
87
|
+
* const response = await admin.graphql(
|
|
88
|
+
* `#graphql
|
|
89
|
+
* query incorrectQuery {
|
|
90
|
+
* products(first: 10) {
|
|
91
|
+
* nodes {
|
|
92
|
+
* not_a_field
|
|
93
|
+
* }
|
|
94
|
+
* }
|
|
95
|
+
* }`,
|
|
96
|
+
* );
|
|
97
|
+
*
|
|
98
|
+
* return json({ data: await response.json() });
|
|
99
|
+
* } catch (error) {
|
|
100
|
+
* if (error instanceof GraphqlQueryError) {
|
|
101
|
+
* // error.body.errors:
|
|
102
|
+
* // { graphQLErrors: [
|
|
103
|
+
* // { message: "Field 'not_a_field' doesn't exist on type 'Product'" }
|
|
104
|
+
* // ] }
|
|
105
|
+
* return json({ errors: error.body?.errors }, { status: 500 });
|
|
106
|
+
* }
|
|
107
|
+
* return json({ message: "An error occurred" }, { status: 500 });
|
|
108
|
+
* }
|
|
109
|
+
* }
|
|
110
|
+
* ```
|
|
111
|
+
*
|
|
112
|
+
* ```ts
|
|
113
|
+
* // /app/shopify.server.ts
|
|
114
|
+
* import { shopifyApp } from "@shopify/shopify-app-remix/server";
|
|
115
|
+
*
|
|
116
|
+
* const shopify = shopifyApp({
|
|
117
|
+
* // ...
|
|
118
|
+
* });
|
|
119
|
+
* export default shopify;
|
|
120
|
+
* export const authenticate = shopify.authenticate;
|
|
121
|
+
* ```
|
|
122
|
+
*/
|
|
123
|
+
graphql: GraphQLClient<AdminOperations>;
|
|
124
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import {Session} from '@shopify/shopify-api';
|
|
2
|
+
|
|
3
|
+
import {BasicParams} from '../../types';
|
|
4
|
+
|
|
5
|
+
import type {StorefrontContext} from '.';
|
|
6
|
+
|
|
7
|
+
export function storefrontClientFactory({
|
|
8
|
+
params,
|
|
9
|
+
session,
|
|
10
|
+
}: {
|
|
11
|
+
params: BasicParams;
|
|
12
|
+
session: Session;
|
|
13
|
+
}): StorefrontContext {
|
|
14
|
+
const {api} = params;
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
graphql: async (query, options = {}) => {
|
|
18
|
+
const client = new api.clients.Storefront({
|
|
19
|
+
session,
|
|
20
|
+
apiVersion: options.apiVersion,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const apiResponse = await client.request(query, {
|
|
24
|
+
variables: options?.variables,
|
|
25
|
+
retries: options?.tries ? options.tries - 1 : 0,
|
|
26
|
+
headers: options?.headers,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
return new Response(JSON.stringify(apiResponse));
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import {StorefrontOperations} from '@shopify/storefront-api-client';
|
|
2
|
+
|
|
3
|
+
import {GraphQLClient} from '../types';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Provides utilities that apps can use to make requests to the Storefront API.
|
|
7
|
+
* @publicDocs
|
|
8
|
+
*/
|
|
9
|
+
export interface StorefrontContext {
|
|
10
|
+
/**
|
|
11
|
+
* Method for interacting with the Shopify Storefront GraphQL API
|
|
12
|
+
*
|
|
13
|
+
* If you're getting incorrect type hints in the Shopify template, follow [these instructions](https://github.com/Shopify/shopify-app-template-remix/tree/main#incorrect-graphql-hints).
|
|
14
|
+
*
|
|
15
|
+
* {@link https://shopify.dev/docs/api/storefront}
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* <caption>Querying the GraphQL API.</caption>
|
|
19
|
+
* <description>Use `storefront.graphql` to make query / mutation requests.</description>
|
|
20
|
+
* ```ts
|
|
21
|
+
* // app/routes/**\/.ts
|
|
22
|
+
* import { json } from "@remix-run/node";
|
|
23
|
+
* import { authenticate } from "../shopify.server";
|
|
24
|
+
*
|
|
25
|
+
* export async function action({ request }: ActionFunctionArgs) {
|
|
26
|
+
* const { storefront } = await authenticate.public.appProxy(request);
|
|
27
|
+
*
|
|
28
|
+
* const response = await storefront.graphql(`{blogs(first: 10) { edges { node { id } } } }`);
|
|
29
|
+
*
|
|
30
|
+
* return json(await response.json());
|
|
31
|
+
* }
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* <caption>Handling GraphQL errors.</caption>
|
|
36
|
+
* <description>Catch `GraphqlQueryError` errors to see error messages from the API.</description>
|
|
37
|
+
* ```ts
|
|
38
|
+
* // /app/routes/**\/*.ts
|
|
39
|
+
* import { ActionFunctionArgs } from "@remix-run/node";
|
|
40
|
+
* import { authenticate } from "../shopify.server";
|
|
41
|
+
*
|
|
42
|
+
* export const action = async ({ request }: ActionFunctionArgs) => {
|
|
43
|
+
* const { storefront } = await authenticate.public.appProxy(request);
|
|
44
|
+
*
|
|
45
|
+
* try {
|
|
46
|
+
* const response = await storefront.graphql(
|
|
47
|
+
* `#graphql
|
|
48
|
+
* query incorrectQuery {
|
|
49
|
+
* products(first: 10) {
|
|
50
|
+
* nodes {
|
|
51
|
+
* not_a_field
|
|
52
|
+
* }
|
|
53
|
+
* }
|
|
54
|
+
* }`,
|
|
55
|
+
* );
|
|
56
|
+
*
|
|
57
|
+
* return json({ data: await response.json() });
|
|
58
|
+
* } catch (error) {
|
|
59
|
+
* if (error instanceof GraphqlQueryError) {
|
|
60
|
+
* // { errors: { graphQLErrors: [
|
|
61
|
+
* // { message: "Field 'not_a_field' doesn't exist on type 'Product'" }
|
|
62
|
+
* // ] } }
|
|
63
|
+
* return json({ errors: error.body?.errors }, { status: 500 });
|
|
64
|
+
* }
|
|
65
|
+
* return json({ message: "An error occurred" }, { status: 500 });
|
|
66
|
+
* }
|
|
67
|
+
* }
|
|
68
|
+
* ```
|
|
69
|
+
*
|
|
70
|
+
* ```ts
|
|
71
|
+
* // /app/shopify.server.ts
|
|
72
|
+
* import { shopifyApp } from "@shopify/shopify-app-remix/server";
|
|
73
|
+
*
|
|
74
|
+
* const shopify = shopifyApp({
|
|
75
|
+
* // ...
|
|
76
|
+
* });
|
|
77
|
+
* export default shopify;
|
|
78
|
+
* export const authenticate = shopify.authenticate;
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
graphql: GraphQLClient<StorefrontOperations>;
|
|
82
|
+
}
|