@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,27 @@
|
|
|
1
|
+
import type {LoaderFunctionArgs} from '@remix-run/node';
|
|
2
|
+
|
|
3
|
+
import {authenticate} from '../shopify.server';
|
|
4
|
+
|
|
5
|
+
export const loader = async ({request}: LoaderFunctionArgs) => {
|
|
6
|
+
const {storefront, liquid} = await authenticate.public.appProxy(request);
|
|
7
|
+
|
|
8
|
+
if (!storefront) {
|
|
9
|
+
return new Response();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const response = await storefront.graphql(
|
|
13
|
+
`#graphql
|
|
14
|
+
query productTitle {
|
|
15
|
+
products(first: 1) {
|
|
16
|
+
nodes {
|
|
17
|
+
title
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}`,
|
|
21
|
+
);
|
|
22
|
+
const body = await response.json();
|
|
23
|
+
|
|
24
|
+
const title = body.data.products.nodes[0].title;
|
|
25
|
+
|
|
26
|
+
return liquid(`Found product ${title} from {{shop.name}}`);
|
|
27
|
+
};
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import {adminClientFactory, storefrontClientFactory} from '../../../clients';
|
|
2
|
+
import {ensureValidOfflineSession} from '../../../helpers';
|
|
3
|
+
import {BasicParams} from '../../../types';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
AppProxyContext,
|
|
7
|
+
AppProxyContextWithSession,
|
|
8
|
+
AuthenticateAppProxy,
|
|
9
|
+
LiquidResponseFunction,
|
|
10
|
+
} from './types';
|
|
11
|
+
|
|
12
|
+
export function authenticateAppProxyFactory(
|
|
13
|
+
params: BasicParams,
|
|
14
|
+
): AuthenticateAppProxy {
|
|
15
|
+
const {logger} = params;
|
|
16
|
+
|
|
17
|
+
return async function authenticate(
|
|
18
|
+
request: Request,
|
|
19
|
+
): Promise<AppProxyContext | AppProxyContextWithSession> {
|
|
20
|
+
const url = new URL(request.url);
|
|
21
|
+
const shop = url.searchParams.get('shop')!;
|
|
22
|
+
logger.info('Authenticating app proxy request', {shop});
|
|
23
|
+
|
|
24
|
+
if (!(await validateAppProxyHmac(params, url))) {
|
|
25
|
+
logger.info('App proxy request has invalid signature', {shop});
|
|
26
|
+
throw new Response(undefined, {
|
|
27
|
+
status: 400,
|
|
28
|
+
statusText: 'Bad Request',
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const session = await ensureValidOfflineSession(params, shop);
|
|
33
|
+
|
|
34
|
+
if (!session) {
|
|
35
|
+
logger.debug('Could not find offline session, returning empty context', {
|
|
36
|
+
shop,
|
|
37
|
+
...Object.fromEntries(url.searchParams.entries()),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const context: AppProxyContext = {
|
|
41
|
+
liquid,
|
|
42
|
+
session: undefined,
|
|
43
|
+
admin: undefined,
|
|
44
|
+
storefront: undefined,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
return context;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const context: AppProxyContextWithSession = {
|
|
51
|
+
liquid,
|
|
52
|
+
session,
|
|
53
|
+
admin: adminClientFactory({params, session}),
|
|
54
|
+
storefront: storefrontClientFactory({params, session}),
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
return context;
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const liquid: LiquidResponseFunction = (body, initAndOptions) => {
|
|
62
|
+
const processedBody = processLiquidBody(body);
|
|
63
|
+
|
|
64
|
+
if (typeof initAndOptions !== 'object') {
|
|
65
|
+
return new Response(processedBody, {
|
|
66
|
+
status: initAndOptions || 200,
|
|
67
|
+
headers: {
|
|
68
|
+
'Content-Type': 'application/liquid',
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const {layout, ...responseInit} = initAndOptions || {};
|
|
74
|
+
const responseBody =
|
|
75
|
+
layout === false ? `{% layout none %} ${processedBody}` : processedBody;
|
|
76
|
+
|
|
77
|
+
const headers = new Headers(responseInit.headers);
|
|
78
|
+
headers.set('Content-Type', 'application/liquid');
|
|
79
|
+
|
|
80
|
+
return new Response(responseBody, {
|
|
81
|
+
...responseInit,
|
|
82
|
+
headers,
|
|
83
|
+
});
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
async function validateAppProxyHmac(
|
|
87
|
+
params: BasicParams,
|
|
88
|
+
url: URL,
|
|
89
|
+
): Promise<boolean> {
|
|
90
|
+
const {api, logger} = params;
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
let searchParams = new URLSearchParams(url.search);
|
|
94
|
+
if (!searchParams.get('index')) {
|
|
95
|
+
searchParams.delete('index');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
let isValid = await api.utils.validateHmac(
|
|
99
|
+
Object.fromEntries(searchParams.entries()),
|
|
100
|
+
{signator: 'appProxy'},
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
if (!isValid) {
|
|
104
|
+
const cleanPath = url.pathname
|
|
105
|
+
.replace(/^\//, '')
|
|
106
|
+
.replace(/\/$/, '')
|
|
107
|
+
.replaceAll('/', '.');
|
|
108
|
+
const data = `routes%2F${cleanPath}`;
|
|
109
|
+
|
|
110
|
+
searchParams = new URLSearchParams(
|
|
111
|
+
`?_data=${data}&${searchParams.toString().replace(/^\?/, '')}`,
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
isValid = await api.utils.validateHmac(
|
|
115
|
+
Object.fromEntries(searchParams.entries()),
|
|
116
|
+
{signator: 'appProxy'},
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
if (!isValid) {
|
|
120
|
+
const searchParams = new URLSearchParams(
|
|
121
|
+
`?_data=${data}._index&${url.search.replace(/^\?/, '')}`,
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
isValid = await api.utils.validateHmac(
|
|
125
|
+
Object.fromEntries(searchParams.entries()),
|
|
126
|
+
{signator: 'appProxy'},
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return isValid;
|
|
132
|
+
} catch (error) {
|
|
133
|
+
const shop = url.searchParams.get('shop')!;
|
|
134
|
+
logger.info(error.message, {shop});
|
|
135
|
+
throw new Response(undefined, {status: 400, statusText: 'Bad Request'});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function processLiquidBody(body: string) {
|
|
140
|
+
return (
|
|
141
|
+
body
|
|
142
|
+
// Add trailing slashes to relative form action URLs
|
|
143
|
+
.replaceAll(
|
|
144
|
+
/<(form[^>]+)action="(\/[^"?]+)(\?[^"]+)?">/g,
|
|
145
|
+
'<$1action="$2/$3">',
|
|
146
|
+
)
|
|
147
|
+
// Add trailing slashes to relative link href URLs
|
|
148
|
+
.replaceAll(/<(a[^>]+)href="(\/[^"?]+)(\?[^"]+)?">/g, '<$1href="$2/$3">')
|
|
149
|
+
);
|
|
150
|
+
}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import {Session} from '@shopify/shopify-api';
|
|
2
|
+
|
|
3
|
+
import {AdminApiContext, StorefrontContext} from '../../../clients';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Authenticates requests coming to the app from Shopify app proxies.
|
|
7
|
+
* @publicDocs
|
|
8
|
+
*/
|
|
9
|
+
export type AuthenticateAppProxy = (
|
|
10
|
+
request: Request,
|
|
11
|
+
) => Promise<AppProxyContext | AppProxyContextWithSession>;
|
|
12
|
+
|
|
13
|
+
interface Options {
|
|
14
|
+
/**
|
|
15
|
+
* Whether to use the shop's theme layout around the Liquid content.
|
|
16
|
+
*/
|
|
17
|
+
layout?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type LiquidResponseFunction = (
|
|
21
|
+
body: string,
|
|
22
|
+
initAndOptions?: number | (ResponseInit & Options),
|
|
23
|
+
) => Response;
|
|
24
|
+
|
|
25
|
+
interface Context {
|
|
26
|
+
/**
|
|
27
|
+
* A utility for creating a Liquid Response.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* <caption>Rendering liquid content.</caption>
|
|
31
|
+
* <description>Use the `liquid` helper to render a `Response` with Liquid content using the shop's theme. See the [Liquid reference](https://shopify.dev/docs/api/liquid) for all the features it enables.</description>
|
|
32
|
+
* ```ts
|
|
33
|
+
* // /app/routes/**\/*.ts
|
|
34
|
+
* import {authenticate} from "~/shopify.server"
|
|
35
|
+
*
|
|
36
|
+
* export async function loader({ request }) {
|
|
37
|
+
* const {liquid} = await authenticate.public.appProxy(request);
|
|
38
|
+
*
|
|
39
|
+
* return liquid("Hello {{shop.name}}");
|
|
40
|
+
* }
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* <caption>Rendering liquid content without a layout.</caption>
|
|
45
|
+
* <description>Set the `layout` option to `false` to render the Liquid content without a theme.</description>
|
|
46
|
+
* ```ts
|
|
47
|
+
* // /app/routes/**\/*.ts
|
|
48
|
+
* import {authenticate} from "~/shopify.server"
|
|
49
|
+
*
|
|
50
|
+
* export async function loader({ request }) {
|
|
51
|
+
* const {liquid} = await authenticate.public.appProxy(request);
|
|
52
|
+
*
|
|
53
|
+
* return liquid(
|
|
54
|
+
* "Hello {{shop.name}}",
|
|
55
|
+
* { layout: false }
|
|
56
|
+
* );
|
|
57
|
+
* }
|
|
58
|
+
* ```
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* <caption>Rendering a form in a Liquid response.</caption>
|
|
62
|
+
* <description>Handle form submissions through an app proxy.</description>
|
|
63
|
+
* ```ts
|
|
64
|
+
* // app/routes/apps.proxy.my-action.tsx
|
|
65
|
+
* import { redirect } from "@remix-run/node";
|
|
66
|
+
* import { authenticate } from "~/shopify.server";
|
|
67
|
+
*
|
|
68
|
+
* export async function loader({ request }) {
|
|
69
|
+
* const { liquid } = await authenticate.public.appProxy(request);
|
|
70
|
+
*
|
|
71
|
+
* return liquid(`
|
|
72
|
+
* <form method="post" action="/apps/proxy/my-action">
|
|
73
|
+
* <input type="text" name="field" />
|
|
74
|
+
* <button type="submit">Submit</button>
|
|
75
|
+
* </form>
|
|
76
|
+
* `);
|
|
77
|
+
* }
|
|
78
|
+
*
|
|
79
|
+
* export async function action({ request }) {
|
|
80
|
+
* await authenticate.public.appProxy(request);
|
|
81
|
+
*
|
|
82
|
+
* const formData = await request.formData();
|
|
83
|
+
* const field = formData.get("field")?.toString();
|
|
84
|
+
*
|
|
85
|
+
* // Perform actions here
|
|
86
|
+
* if (field) {
|
|
87
|
+
* console.log("Field:", field);
|
|
88
|
+
* }
|
|
89
|
+
*
|
|
90
|
+
* // Return to the form page
|
|
91
|
+
* return redirect("/apps/proxy/my-action");
|
|
92
|
+
* }
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
liquid: LiquidResponseFunction;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface AppProxyContext extends Context {
|
|
99
|
+
/**
|
|
100
|
+
* No session is available for the shop that made this request.
|
|
101
|
+
*
|
|
102
|
+
* This comes from the session storage which `shopifyApp` uses to store sessions in your database of choice.
|
|
103
|
+
*/
|
|
104
|
+
session: undefined;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* No session is available for the shop that made this request.
|
|
108
|
+
* Therefore no methods for interacting with the GraphQL Admin API are available.
|
|
109
|
+
*/
|
|
110
|
+
admin: undefined;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* No session is available for the shop that made this request.
|
|
114
|
+
* Therefore no method for interacting with the Storefront API is available.
|
|
115
|
+
*/
|
|
116
|
+
storefront: undefined;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export interface AppProxyContextWithSession extends Context {
|
|
120
|
+
/**
|
|
121
|
+
* The session for the shop that made the request.
|
|
122
|
+
*
|
|
123
|
+
* This comes from the session storage which `shopifyApp` uses to store sessions in your database of choice.
|
|
124
|
+
*
|
|
125
|
+
* Use this to get shop or user-specific data.
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* <caption>Using the session object.</caption>
|
|
129
|
+
* <description>Get the session for the shop that initiated the request to the app proxy.</description>
|
|
130
|
+
* ```ts
|
|
131
|
+
* // app/routes/**\/.ts
|
|
132
|
+
* import { json } from "@remix-run/node";
|
|
133
|
+
* import { authenticate } from "../shopify.server";
|
|
134
|
+
* import { getMyAppModelData } from "~/db/model.server";
|
|
135
|
+
*
|
|
136
|
+
* export const loader = async ({ request }) => {
|
|
137
|
+
* // Get the session for the shop that initiated the request to the app proxy.
|
|
138
|
+
* const { session } =
|
|
139
|
+
* await authenticate.public.appProxy(request);
|
|
140
|
+
*
|
|
141
|
+
* // Use the session data to make to queries to your database or additional requests.
|
|
142
|
+
* return json(
|
|
143
|
+
* await getMyAppModelData({shop: session.shop})
|
|
144
|
+
* );
|
|
145
|
+
* };
|
|
146
|
+
* ```
|
|
147
|
+
*/
|
|
148
|
+
session: Session;
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Methods for interacting with the GraphQL Admin API for the store that made the request.
|
|
152
|
+
*
|
|
153
|
+
* @example
|
|
154
|
+
* <caption>Interacting with the Admin API.</caption>
|
|
155
|
+
* <description>Use the `admin` object to interact with the admin GraphQL API.</description>
|
|
156
|
+
* ```ts
|
|
157
|
+
* // app/routes/**\/.ts
|
|
158
|
+
* import { json } from "@remix-run/node";
|
|
159
|
+
* import { authenticate } from "../shopify.server";
|
|
160
|
+
*
|
|
161
|
+
* export async function action({ request }: ActionFunctionArgs) {
|
|
162
|
+
* const { admin } = await authenticate.public.appProxy(request);
|
|
163
|
+
*
|
|
164
|
+
* const response = await admin.graphql(
|
|
165
|
+
* `#graphql
|
|
166
|
+
* mutation populateProduct($input: ProductInput!) {
|
|
167
|
+
* productCreate(input: $input) {
|
|
168
|
+
* product {
|
|
169
|
+
* id
|
|
170
|
+
* }
|
|
171
|
+
* }
|
|
172
|
+
* }`,
|
|
173
|
+
* {
|
|
174
|
+
* variables: {
|
|
175
|
+
* input: { title: "Product Name" }
|
|
176
|
+
* }
|
|
177
|
+
* }
|
|
178
|
+
* );
|
|
179
|
+
*
|
|
180
|
+
* const productData = await response.json();
|
|
181
|
+
* return json({ data: productData.data });
|
|
182
|
+
* }
|
|
183
|
+
* ```
|
|
184
|
+
*/
|
|
185
|
+
admin: AdminApiContext;
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Method for interacting with the Shopify Storefront Graphql API for the store that made the request.
|
|
189
|
+
*
|
|
190
|
+
* @example
|
|
191
|
+
* <caption>Interacting with the Storefront API.</caption>
|
|
192
|
+
* <description>Use the `storefront` object to interact with the GraphQL API.</description>
|
|
193
|
+
* ```ts
|
|
194
|
+
* // app/routes/**\/.ts
|
|
195
|
+
* import { json } from "@remix-run/node";
|
|
196
|
+
* import { authenticate } from "../shopify.server";
|
|
197
|
+
*
|
|
198
|
+
* export async function action({ request }: ActionFunctionArgs) {
|
|
199
|
+
* const { storefront } = await authenticate.public.appProxy(request);
|
|
200
|
+
*
|
|
201
|
+
* const response = await storefront.graphql(
|
|
202
|
+
* `#graphql
|
|
203
|
+
* query blogIds {
|
|
204
|
+
* blogs(first: 10) {
|
|
205
|
+
* edges {
|
|
206
|
+
* node {
|
|
207
|
+
* id
|
|
208
|
+
* }
|
|
209
|
+
* }
|
|
210
|
+
* }
|
|
211
|
+
* }`
|
|
212
|
+
* );
|
|
213
|
+
*
|
|
214
|
+
* return json(await response.json());
|
|
215
|
+
* }
|
|
216
|
+
* ```
|
|
217
|
+
*/
|
|
218
|
+
storefront: StorefrontContext;
|
|
219
|
+
}
|
package/src/server/authenticate/public/checkout/authenticate.public.checkout.doc.example.offers.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Most apps would load this from their database
|
|
2
|
+
export function getOffers(shop: string) {
|
|
3
|
+
const offers: Record<any, any[]> = {
|
|
4
|
+
'shop.com': [
|
|
5
|
+
{
|
|
6
|
+
id: '1',
|
|
7
|
+
title: '10% off',
|
|
8
|
+
price: 10,
|
|
9
|
+
type: 'percentage',
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
id: '2',
|
|
13
|
+
title: 'Free shipping',
|
|
14
|
+
price: 0,
|
|
15
|
+
type: 'shipping',
|
|
16
|
+
},
|
|
17
|
+
],
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
return offers[shop];
|
|
21
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type {ActionFunctionArgs, LoaderFunctionArgs} from '@remix-run/node';
|
|
2
|
+
import {json} from '@remix-run/node';
|
|
3
|
+
|
|
4
|
+
import {authenticate} from '../shopify.server';
|
|
5
|
+
import {getOffers} from '../offers.server';
|
|
6
|
+
|
|
7
|
+
// The loader responds to preflight requests from Shopify
|
|
8
|
+
export const loader = async ({request}: LoaderFunctionArgs) => {
|
|
9
|
+
await authenticate.public.checkout(request);
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const action = async ({request}: ActionFunctionArgs) => {
|
|
13
|
+
const {cors, sessionToken} = await authenticate.public.checkout(request);
|
|
14
|
+
|
|
15
|
+
const offers = getOffers(sessionToken.dest);
|
|
16
|
+
return cors(json({offers}));
|
|
17
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type {BasicParams} from '../../../types';
|
|
2
|
+
import {authenticateExtensionFactory} from '../extension/authenticate';
|
|
3
|
+
|
|
4
|
+
import type {AuthenticateCheckout} from './types';
|
|
5
|
+
|
|
6
|
+
export function authenticateCheckoutFactory(
|
|
7
|
+
params: BasicParams,
|
|
8
|
+
): AuthenticateCheckout {
|
|
9
|
+
return authenticateExtensionFactory(
|
|
10
|
+
params,
|
|
11
|
+
'checkout',
|
|
12
|
+
) as AuthenticateCheckout;
|
|
13
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import {JwtPayload} from '@shopify/shopify-api';
|
|
2
|
+
|
|
3
|
+
import {EnsureCORSFunction} from '../../helpers/ensure-cors-headers';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Authenticates requests coming from Shopify checkout extensions.
|
|
7
|
+
* @publicDocs
|
|
8
|
+
*/
|
|
9
|
+
export type AuthenticateCheckout = (
|
|
10
|
+
request: Request,
|
|
11
|
+
options?: AuthenticateCheckoutOptions,
|
|
12
|
+
) => Promise<CheckoutContext>;
|
|
13
|
+
|
|
14
|
+
export interface AuthenticateCheckoutOptions {
|
|
15
|
+
corsHeaders?: string[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Authenticated Context for a checkout request
|
|
20
|
+
*/
|
|
21
|
+
export interface CheckoutContext {
|
|
22
|
+
/**
|
|
23
|
+
* The decoded and validated session token for the request
|
|
24
|
+
*
|
|
25
|
+
* Refer to the OAuth docs for the [session token payload](https://shopify.dev/docs/apps/auth/oauth/session-tokens#payload).
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* <caption>Using the decoded session token.</caption>
|
|
29
|
+
* <description>Get store-specific data using the `sessionToken` object.</description>
|
|
30
|
+
* ```ts
|
|
31
|
+
* // app/routes/public/my-route.ts
|
|
32
|
+
* import { LoaderFunctionArgs, json } from "@remix-run/node";
|
|
33
|
+
* import { authenticate } from "../shopify.server";
|
|
34
|
+
* import { getMyAppData } from "~/db/model.server";
|
|
35
|
+
*
|
|
36
|
+
* export const loader = async ({ request }: LoaderFunctionArgs) => {
|
|
37
|
+
* const { sessionToken } = await authenticate.public.checkout(
|
|
38
|
+
* request
|
|
39
|
+
* );
|
|
40
|
+
* return json(await getMyAppData({shop: sessionToken.dest}));
|
|
41
|
+
* };
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
sessionToken: JwtPayload;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* A function that ensures the CORS headers are set correctly for the response.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* <caption>Setting CORS headers for a public request.</caption>
|
|
51
|
+
* <description>Use the `cors` helper to ensure your app can respond to checkout extension requests.</description>
|
|
52
|
+
* ```ts
|
|
53
|
+
* // app/routes/public/my-route.ts
|
|
54
|
+
* import { LoaderFunctionArgs, json } from "@remix-run/node";
|
|
55
|
+
* import { authenticate } from "../shopify.server";
|
|
56
|
+
* import { getMyAppData } from "~/db/model.server";
|
|
57
|
+
*
|
|
58
|
+
* export const loader = async ({ request }: LoaderFunctionArgs) => {
|
|
59
|
+
* const { sessionToken, cors } = await authenticate.public.checkout(
|
|
60
|
+
* request,
|
|
61
|
+
* { corsHeaders: ["X-My-Custom-Header"] }
|
|
62
|
+
* );
|
|
63
|
+
* const data = await getMyAppData({shop: sessionToken.dest});
|
|
64
|
+
* return cors(json(data));
|
|
65
|
+
* };
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
cors: EnsureCORSFunction;
|
|
69
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Most apps would load this from their database
|
|
2
|
+
export function getOffers(shop: string, customerID: string) {
|
|
3
|
+
const offers: Record<string, any[]> = {
|
|
4
|
+
'shop.com': [
|
|
5
|
+
{
|
|
6
|
+
id: '1',
|
|
7
|
+
title: '10% off',
|
|
8
|
+
price: 10,
|
|
9
|
+
type: 'percentage',
|
|
10
|
+
customerId: 'gid://shopify/Customer/1001',
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
id: '2',
|
|
14
|
+
title: 'Free shipping',
|
|
15
|
+
price: 0,
|
|
16
|
+
type: 'shipping',
|
|
17
|
+
customerId: 'gid://shopify/Customer/1001',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
id: '3',
|
|
21
|
+
title: '5% off',
|
|
22
|
+
price: 5,
|
|
23
|
+
type: 'percentage',
|
|
24
|
+
customerId: 'gid://shopify/Customer/1001',
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const allOffers = offers[shop] || [];
|
|
30
|
+
// Filter offers to include only those that match the customerId
|
|
31
|
+
const filteredOffers = allOffers.filter(
|
|
32
|
+
(offer) => offer.customerId === customerID,
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
return filteredOffers;
|
|
36
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type {ActionFunctionArgs, LoaderFunctionArgs} from '@remix-run/node';
|
|
2
|
+
import {json} from '@remix-run/node';
|
|
3
|
+
|
|
4
|
+
import {authenticate} from '../shopify.server';
|
|
5
|
+
import {getOffers} from '../offers.server';
|
|
6
|
+
|
|
7
|
+
// The loader responds to preflight requests from Shopify
|
|
8
|
+
export const loader = async ({request}: LoaderFunctionArgs) => {
|
|
9
|
+
await authenticate.public.customerAccount(request);
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const action = async ({request}: ActionFunctionArgs) => {
|
|
13
|
+
const {cors, sessionToken} = await authenticate.public.customerAccount(request);
|
|
14
|
+
|
|
15
|
+
// Get offers for the customer
|
|
16
|
+
const offers = getOffers(sessionToken.des, sessionToken.sub);
|
|
17
|
+
return cors(json({offers}));
|
|
18
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import {BasicParams} from '../../../types';
|
|
2
|
+
import {authenticateExtensionFactory} from '../extension/authenticate';
|
|
3
|
+
|
|
4
|
+
import {AuthenticateCustomerAccount} from './types';
|
|
5
|
+
|
|
6
|
+
export function authenticateCustomerAccountFactory(
|
|
7
|
+
params: BasicParams,
|
|
8
|
+
): AuthenticateCustomerAccount {
|
|
9
|
+
return authenticateExtensionFactory(
|
|
10
|
+
params,
|
|
11
|
+
'customer account',
|
|
12
|
+
) as AuthenticateCustomerAccount;
|
|
13
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import {JwtPayload} from '@shopify/shopify-api';
|
|
2
|
+
|
|
3
|
+
import {EnsureCORSFunction} from '../../helpers/ensure-cors-headers';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Authenticates requests coming from Shopify customer account extensions.
|
|
7
|
+
* @publicDocs
|
|
8
|
+
*/
|
|
9
|
+
export type AuthenticateCustomerAccount = (
|
|
10
|
+
request: Request,
|
|
11
|
+
options?: AuthenticateCustomerAccountOptions,
|
|
12
|
+
) => Promise<CustomerAccountContext>;
|
|
13
|
+
|
|
14
|
+
export interface AuthenticateCustomerAccountOptions {
|
|
15
|
+
corsHeaders?: string[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Authenticated Context for a customer account extension request
|
|
20
|
+
*/
|
|
21
|
+
export interface CustomerAccountContext {
|
|
22
|
+
/**
|
|
23
|
+
* The decoded and validated session token for the request
|
|
24
|
+
*
|
|
25
|
+
* Refer to the OAuth docs for the [session token payload](https://shopify.dev/docs/apps/auth/oauth/session-tokens#payload).
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* <caption>Using the decoded session token.</caption>
|
|
29
|
+
* <description>Get store-specific data using the `sessionToken` object.</description>
|
|
30
|
+
* ```ts
|
|
31
|
+
* // app/routes/public/my-route.ts
|
|
32
|
+
* import { LoaderFunctionArgs, json } from "@remix-run/node";
|
|
33
|
+
* import { authenticate } from "../shopify.server";
|
|
34
|
+
* import { getMyAppData } from "~/db/model.server";
|
|
35
|
+
*
|
|
36
|
+
* export const loader = async ({ request }: LoaderFunctionArgs) => {
|
|
37
|
+
* const { sessionToken } = await authenticate.public.customerAccount(
|
|
38
|
+
* request
|
|
39
|
+
* );
|
|
40
|
+
* return json(await getMyAppData({shop: sessionToken.dest}));
|
|
41
|
+
* };
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
sessionToken: JwtPayload;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* A function that ensures the CORS headers are set correctly for the response.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* <caption>Setting CORS headers for a public request.</caption>
|
|
51
|
+
* <description>Use the `cors` helper to ensure your app can respond to customer account extension requests.</description>
|
|
52
|
+
* ```ts
|
|
53
|
+
* // app/routes/public/my-route.ts
|
|
54
|
+
* import { LoaderFunctionArgs, json } from "@remix-run/node";
|
|
55
|
+
* import { authenticate } from "../shopify.server";
|
|
56
|
+
* import { getMyAppData } from "~/db/model.server";
|
|
57
|
+
*
|
|
58
|
+
* export const loader = async ({ request }: LoaderFunctionArgs) => {
|
|
59
|
+
* const { sessionToken, cors } = await authenticate.public.customerAccount(
|
|
60
|
+
* request,
|
|
61
|
+
* { corsHeaders: ["X-My-Custom-Header"] }
|
|
62
|
+
* );
|
|
63
|
+
* const data = await getMyAppData({shop: sessionToken.dest});
|
|
64
|
+
* return cors(json(data));
|
|
65
|
+
* };
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
cors: EnsureCORSFunction;
|
|
69
|
+
}
|