@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,78 @@
|
|
|
1
|
+
import {
|
|
2
|
+
RequestType,
|
|
3
|
+
ValidRequestOptions as ValidRequestOptionsImport,
|
|
4
|
+
setUpValidRequest as setUpValidRequestImport,
|
|
5
|
+
} from '@shopify/shopify-api/test-helpers';
|
|
6
|
+
|
|
7
|
+
import {ShopifyApp} from '../types';
|
|
8
|
+
|
|
9
|
+
import {API_KEY, API_SECRET_KEY, APP_URL, TEST_SHOP_NAME} from './const';
|
|
10
|
+
import {setUpValidSession} from './setup-valid-session';
|
|
11
|
+
|
|
12
|
+
interface ValidBaseRequestOptions {
|
|
13
|
+
type: RequestType.Admin | RequestType.Bearer | RequestType.Public;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface ValidExtensionRequestOptions {
|
|
17
|
+
type: RequestType.Extension;
|
|
18
|
+
body?: any;
|
|
19
|
+
headers?: Record<string, string>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type ValidRequestOptions =
|
|
23
|
+
| ValidBaseRequestOptions
|
|
24
|
+
| ValidExtensionRequestOptions;
|
|
25
|
+
|
|
26
|
+
export async function setupValidRequest(
|
|
27
|
+
shopify: ShopifyApp<any>,
|
|
28
|
+
options: ValidRequestOptions,
|
|
29
|
+
) {
|
|
30
|
+
const session = await setUpValidSession(shopify.sessionStorage, {
|
|
31
|
+
isOnline: false,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
let decoratedOptions: ValidRequestOptionsImport;
|
|
35
|
+
switch (options.type) {
|
|
36
|
+
case RequestType.Admin:
|
|
37
|
+
decoratedOptions = {
|
|
38
|
+
store: TEST_SHOP_NAME,
|
|
39
|
+
apiKey: API_KEY,
|
|
40
|
+
apiSecretKey: API_SECRET_KEY,
|
|
41
|
+
...options,
|
|
42
|
+
};
|
|
43
|
+
break;
|
|
44
|
+
case RequestType.Bearer:
|
|
45
|
+
decoratedOptions = {
|
|
46
|
+
store: TEST_SHOP_NAME,
|
|
47
|
+
apiKey: API_KEY,
|
|
48
|
+
apiSecretKey: API_SECRET_KEY,
|
|
49
|
+
...options,
|
|
50
|
+
};
|
|
51
|
+
break;
|
|
52
|
+
case RequestType.Extension:
|
|
53
|
+
decoratedOptions = {
|
|
54
|
+
store: TEST_SHOP_NAME,
|
|
55
|
+
apiSecretKey: API_SECRET_KEY,
|
|
56
|
+
...options,
|
|
57
|
+
};
|
|
58
|
+
break;
|
|
59
|
+
case RequestType.Public:
|
|
60
|
+
decoratedOptions = {
|
|
61
|
+
store: TEST_SHOP_NAME,
|
|
62
|
+
apiSecretKey: API_SECRET_KEY,
|
|
63
|
+
...options,
|
|
64
|
+
type: options.type,
|
|
65
|
+
};
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const url = new URL(APP_URL);
|
|
70
|
+
const request: Request = await setUpValidRequestImport(
|
|
71
|
+
decoratedOptions,
|
|
72
|
+
new Request(url),
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
return {shopify, session, request};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export {RequestType};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import {SessionStorage} from '@shopify/shopify-app-session-storage';
|
|
2
|
+
import {Session} from '@shopify/shopify-api';
|
|
3
|
+
import {setUpValidSession as setUpValidSessionImport} from '@shopify/shopify-api/test-helpers';
|
|
4
|
+
|
|
5
|
+
import {TEST_SHOP} from './const';
|
|
6
|
+
|
|
7
|
+
export async function setUpValidSession(
|
|
8
|
+
sessionStorage: SessionStorage,
|
|
9
|
+
sessionParams?: Partial<Session>,
|
|
10
|
+
): Promise<Session> {
|
|
11
|
+
const session = setUpValidSessionImport({
|
|
12
|
+
shop: TEST_SHOP,
|
|
13
|
+
...sessionParams,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
await sessionStorage.storeSession(session);
|
|
17
|
+
|
|
18
|
+
return session;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function setupValidCustomAppSession(shop: string): Session {
|
|
22
|
+
return new Session({
|
|
23
|
+
id: '',
|
|
24
|
+
shop,
|
|
25
|
+
state: '',
|
|
26
|
+
isOnline: false,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Cookies,
|
|
3
|
+
NormalizedRequest,
|
|
4
|
+
NormalizedResponse,
|
|
5
|
+
} from '@shopify/shopify-api/runtime';
|
|
6
|
+
|
|
7
|
+
import {API_SECRET_KEY} from './const';
|
|
8
|
+
|
|
9
|
+
export async function signRequestCookie({
|
|
10
|
+
request,
|
|
11
|
+
cookieName,
|
|
12
|
+
cookieValue,
|
|
13
|
+
}: {
|
|
14
|
+
request: Request;
|
|
15
|
+
cookieName: string;
|
|
16
|
+
cookieValue: string;
|
|
17
|
+
}) {
|
|
18
|
+
const cookies = new Cookies(
|
|
19
|
+
{headers: {}} as NormalizedRequest,
|
|
20
|
+
{} as NormalizedResponse,
|
|
21
|
+
{keys: [API_SECRET_KEY]},
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
await cookies.setAndSign(cookieName, cookieValue);
|
|
25
|
+
|
|
26
|
+
const cookieHeader = cookies
|
|
27
|
+
.toHeaders()
|
|
28
|
+
.map((cookie) => cookie.split(';')[0])
|
|
29
|
+
.join(';');
|
|
30
|
+
|
|
31
|
+
request.headers.set('Cookie', cookieHeader);
|
|
32
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import {ApiVersion} from '@shopify/shopify-api';
|
|
2
|
+
import {MemorySessionStorage} from '@shopify/shopify-app-session-storage-memory';
|
|
3
|
+
|
|
4
|
+
import type {FutureFlagOptions, FutureFlags} from '../future/flags';
|
|
5
|
+
import type {TestOverridesArg} from '../test-helpers/test-config';
|
|
6
|
+
import {testConfig as testConfigImport} from '../test-helpers/test-config';
|
|
7
|
+
|
|
8
|
+
/*
|
|
9
|
+
* This object mandates that all existing future flags be activated for tests. If a new flag is added, this object must
|
|
10
|
+
* be updated to include it, which will also cause all tests to use the new behaviour by default (and likely fail).
|
|
11
|
+
*
|
|
12
|
+
* This way, we'll always ensure our tests are covering all future flags. Please make sure to also have tests for the
|
|
13
|
+
* old behaviour.
|
|
14
|
+
*/
|
|
15
|
+
const TEST_FUTURE_FLAGS: Required<{[key in keyof FutureFlags]: true}> = {
|
|
16
|
+
unstable_newEmbeddedAuthStrategy: true,
|
|
17
|
+
expiringOfflineAccessTokens: true,
|
|
18
|
+
} as const;
|
|
19
|
+
|
|
20
|
+
// Override the helper's future flags and logger settings for our purposes
|
|
21
|
+
const TEST_CONFIG = {
|
|
22
|
+
apiVersion: ApiVersion.July25,
|
|
23
|
+
isEmbeddedApp: true,
|
|
24
|
+
future: TEST_FUTURE_FLAGS,
|
|
25
|
+
logger: {
|
|
26
|
+
log: jest.fn(),
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// Reset the config object before each test
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
TEST_CONFIG.logger.log.mockReset();
|
|
33
|
+
(TEST_CONFIG as any).sessionStorage = new MemorySessionStorage();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
export function testConfig<
|
|
37
|
+
Overrides extends TestOverridesArg,
|
|
38
|
+
Future extends FutureFlagOptions,
|
|
39
|
+
>(
|
|
40
|
+
{future, ...overrides}: Overrides & {future?: Future} = {} as Overrides & {
|
|
41
|
+
future?: Future;
|
|
42
|
+
},
|
|
43
|
+
) {
|
|
44
|
+
return testConfigImport({
|
|
45
|
+
...TEST_CONFIG,
|
|
46
|
+
...overrides,
|
|
47
|
+
future: {
|
|
48
|
+
...TEST_CONFIG.future,
|
|
49
|
+
...future,
|
|
50
|
+
},
|
|
51
|
+
logger: {
|
|
52
|
+
...TEST_CONFIG.logger,
|
|
53
|
+
...(overrides as NonNullable<Overrides>).logger,
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import {setAbstractRuntimeString} from '@shopify/shopify-api/runtime';
|
|
2
|
+
|
|
3
|
+
import {setAppBridgeUrlOverride} from '../../authenticate/helpers';
|
|
4
|
+
|
|
5
|
+
setAbstractRuntimeString(() => {
|
|
6
|
+
return `Remix (Node)`;
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
/* eslint-disable no-process-env */
|
|
10
|
+
if (process.env.APP_BRIDGE_URL) {
|
|
11
|
+
setAppBridgeUrlOverride(process.env.APP_BRIDGE_URL);
|
|
12
|
+
}
|
|
13
|
+
/* eslint-enable no-process-env */
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import {setAbstractFetchFunc} from '@shopify/shopify-api/runtime';
|
|
2
|
+
import {installGlobals} from '@remix-run/node';
|
|
3
|
+
|
|
4
|
+
import '../node';
|
|
5
|
+
|
|
6
|
+
installGlobals();
|
|
7
|
+
|
|
8
|
+
const fetchFunc = fetch;
|
|
9
|
+
setAbstractFetchFunc(async (...args) => {
|
|
10
|
+
const response = await fetchFunc(...args);
|
|
11
|
+
|
|
12
|
+
return response as any;
|
|
13
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import {type ActionFunctionArgs, json} from '@remix-run/node';
|
|
2
|
+
import {GraphqlQueryError} from '@shopify/shopify-api';
|
|
3
|
+
|
|
4
|
+
import {authenticate} from '../shopify.server';
|
|
5
|
+
|
|
6
|
+
export const action = async ({request}: ActionFunctionArgs) => {
|
|
7
|
+
const {admin, redirect} = await authenticate.admin(request);
|
|
8
|
+
|
|
9
|
+
try {
|
|
10
|
+
await admin.graphql(
|
|
11
|
+
`#graphql
|
|
12
|
+
mutation updateProductTitle($input: ProductInput!) {
|
|
13
|
+
productUpdate(input: $input) {
|
|
14
|
+
product {
|
|
15
|
+
id
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}`,
|
|
19
|
+
{
|
|
20
|
+
variables: {
|
|
21
|
+
input: {id: '123', title: 'New title'},
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
return redirect('/app/product-updated');
|
|
27
|
+
} catch (error) {
|
|
28
|
+
if (error instanceof GraphqlQueryError) {
|
|
29
|
+
return json({errors: error.body?.errors}, {status: 500});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return new Response('Failed to update product title', {status: 500});
|
|
33
|
+
}
|
|
34
|
+
};
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import {JwtPayload, Session} from '@shopify/shopify-api';
|
|
2
|
+
|
|
3
|
+
import type {BasicParams} from '../../types';
|
|
4
|
+
import type {AppConfigArg} from '../../config-types';
|
|
5
|
+
import {
|
|
6
|
+
getSessionTokenHeader,
|
|
7
|
+
ensureCORSHeadersFactory,
|
|
8
|
+
getSessionTokenFromUrlParam,
|
|
9
|
+
respondToBotRequest,
|
|
10
|
+
respondToOptionsRequest,
|
|
11
|
+
validateSessionToken,
|
|
12
|
+
getShopFromRequest,
|
|
13
|
+
} from '../helpers';
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
cancelBillingFactory,
|
|
17
|
+
requestBillingFactory,
|
|
18
|
+
requireBillingFactory,
|
|
19
|
+
checkBillingFactory,
|
|
20
|
+
createUsageRecordFactory,
|
|
21
|
+
updateUsageCappedAmountFactory,
|
|
22
|
+
} from './billing';
|
|
23
|
+
import type {
|
|
24
|
+
AdminContext,
|
|
25
|
+
AuthenticateAdmin,
|
|
26
|
+
EmbeddedAdminContext,
|
|
27
|
+
NonEmbeddedAdminContext,
|
|
28
|
+
} from './types';
|
|
29
|
+
import {
|
|
30
|
+
createAdminApiContext,
|
|
31
|
+
ensureAppIsEmbeddedIfRequired,
|
|
32
|
+
ensureSessionTokenSearchParamIfRequired,
|
|
33
|
+
redirectFactory,
|
|
34
|
+
renderAppBridge,
|
|
35
|
+
validateShopAndHostParams,
|
|
36
|
+
} from './helpers';
|
|
37
|
+
import {AuthorizationStrategy} from './strategies/types';
|
|
38
|
+
import {scopesApiFactory} from './scope/factory';
|
|
39
|
+
|
|
40
|
+
export interface SessionTokenContext {
|
|
41
|
+
shop: string;
|
|
42
|
+
sessionId?: string;
|
|
43
|
+
sessionToken?: string;
|
|
44
|
+
payload?: JwtPayload;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface AuthStrategyParams extends BasicParams {
|
|
48
|
+
strategy: AuthorizationStrategy;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function authStrategyFactory<ConfigArg extends AppConfigArg>({
|
|
52
|
+
strategy,
|
|
53
|
+
...params
|
|
54
|
+
}: AuthStrategyParams): AuthenticateAdmin<ConfigArg> {
|
|
55
|
+
const {api, logger, config} = params;
|
|
56
|
+
|
|
57
|
+
async function respondToBouncePageRequest(request: Request) {
|
|
58
|
+
const url = new URL(request.url);
|
|
59
|
+
|
|
60
|
+
if (url.pathname === config.auth.patchSessionTokenPath) {
|
|
61
|
+
logger.debug('Rendering bounce page', {
|
|
62
|
+
shop: getShopFromRequest(request),
|
|
63
|
+
});
|
|
64
|
+
throw renderAppBridge({config, logger, api}, request);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function respondToExitIframeRequest(request: Request) {
|
|
69
|
+
const url = new URL(request.url);
|
|
70
|
+
|
|
71
|
+
if (url.pathname === config.auth.exitIframePath) {
|
|
72
|
+
const destination = url.searchParams.get('exitIframe')!;
|
|
73
|
+
|
|
74
|
+
logger.debug('Rendering exit iframe page', {
|
|
75
|
+
shop: getShopFromRequest(request),
|
|
76
|
+
destination,
|
|
77
|
+
});
|
|
78
|
+
throw renderAppBridge({config, logger, api}, request, {url: destination});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
type AdminContextBase =
|
|
83
|
+
| EmbeddedAdminContext<ConfigArg>
|
|
84
|
+
| NonEmbeddedAdminContext<ConfigArg>;
|
|
85
|
+
|
|
86
|
+
function createContext(
|
|
87
|
+
request: Request,
|
|
88
|
+
session: Session,
|
|
89
|
+
authStrategy: AuthorizationStrategy,
|
|
90
|
+
sessionToken?: JwtPayload,
|
|
91
|
+
): AdminContext<ConfigArg> {
|
|
92
|
+
let context: AdminContextBase = {
|
|
93
|
+
admin: createAdminApiContext(
|
|
94
|
+
session,
|
|
95
|
+
params,
|
|
96
|
+
authStrategy.handleClientError(request),
|
|
97
|
+
),
|
|
98
|
+
billing: {
|
|
99
|
+
require: requireBillingFactory(params, request, session),
|
|
100
|
+
check: checkBillingFactory(params, request, session),
|
|
101
|
+
request: requestBillingFactory(params, request, session),
|
|
102
|
+
cancel: cancelBillingFactory(params, request, session),
|
|
103
|
+
createUsageRecord: createUsageRecordFactory(params, request, session),
|
|
104
|
+
updateUsageCappedAmount: updateUsageCappedAmountFactory(
|
|
105
|
+
params,
|
|
106
|
+
request,
|
|
107
|
+
session,
|
|
108
|
+
),
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
session,
|
|
112
|
+
cors: ensureCORSHeadersFactory(params, request),
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
context = addEmbeddedFeatures(context, request, session, sessionToken);
|
|
116
|
+
context = addScopesFeatures(context);
|
|
117
|
+
|
|
118
|
+
return context as AdminContext<ConfigArg>;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function addEmbeddedFeatures(
|
|
122
|
+
context: AdminContextBase,
|
|
123
|
+
request: Request,
|
|
124
|
+
session: Session,
|
|
125
|
+
sessionToken?: JwtPayload,
|
|
126
|
+
) {
|
|
127
|
+
if (config.isEmbeddedApp) {
|
|
128
|
+
return {
|
|
129
|
+
...context,
|
|
130
|
+
sessionToken,
|
|
131
|
+
redirect: redirectFactory(params, request, session.shop),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
return context;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function addScopesFeatures(context: AdminContextBase) {
|
|
138
|
+
return {
|
|
139
|
+
...context,
|
|
140
|
+
scopes: scopesApiFactory(params, context.session, context.admin),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return async function authenticateAdmin(request: Request) {
|
|
145
|
+
try {
|
|
146
|
+
respondToBotRequest(params, request);
|
|
147
|
+
respondToOptionsRequest(params, request);
|
|
148
|
+
await respondToBouncePageRequest(request);
|
|
149
|
+
await respondToExitIframeRequest(request);
|
|
150
|
+
await strategy.respondToOAuthRequests(request);
|
|
151
|
+
|
|
152
|
+
// If this is a valid request, but it doesn't have a session token header, this is a document request. We need to
|
|
153
|
+
// ensure we're embedded if needed and we have the information needed to load the session.
|
|
154
|
+
if (!getSessionTokenHeader(request)) {
|
|
155
|
+
validateShopAndHostParams(params, request);
|
|
156
|
+
await ensureAppIsEmbeddedIfRequired(params, request);
|
|
157
|
+
await ensureSessionTokenSearchParamIfRequired(params, request);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
logger.info('Authenticating admin request', {
|
|
161
|
+
shop: getShopFromRequest(request),
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
const {payload, shop, sessionId, sessionToken} =
|
|
165
|
+
await getSessionTokenContext(params, request);
|
|
166
|
+
|
|
167
|
+
logger.debug('Loading session from storage', {shop, sessionId});
|
|
168
|
+
const existingSession = sessionId
|
|
169
|
+
? await config.sessionStorage!.loadSession(sessionId)
|
|
170
|
+
: undefined;
|
|
171
|
+
|
|
172
|
+
const session = await strategy.authenticate(request, {
|
|
173
|
+
session: existingSession,
|
|
174
|
+
sessionToken,
|
|
175
|
+
shop,
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
return createContext(request, session, strategy, payload);
|
|
179
|
+
} catch (errorOrResponse) {
|
|
180
|
+
if (errorOrResponse instanceof Response) {
|
|
181
|
+
logger.debug('Authenticate returned a response', {
|
|
182
|
+
shop: getShopFromRequest(request),
|
|
183
|
+
});
|
|
184
|
+
ensureCORSHeadersFactory(params, request)(errorOrResponse);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
throw errorOrResponse;
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async function getSessionTokenContext(
|
|
193
|
+
params: BasicParams,
|
|
194
|
+
request: Request,
|
|
195
|
+
): Promise<SessionTokenContext> {
|
|
196
|
+
const {api, config, logger} = params;
|
|
197
|
+
|
|
198
|
+
const headerSessionToken = getSessionTokenHeader(request);
|
|
199
|
+
const searchParamSessionToken = getSessionTokenFromUrlParam(request);
|
|
200
|
+
const sessionToken = (headerSessionToken || searchParamSessionToken)!;
|
|
201
|
+
|
|
202
|
+
logger.debug('Attempting to authenticate session token', {
|
|
203
|
+
shop: getShopFromRequest(request),
|
|
204
|
+
sessionToken: JSON.stringify({
|
|
205
|
+
header: headerSessionToken,
|
|
206
|
+
search: searchParamSessionToken,
|
|
207
|
+
}),
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
if (config.isEmbeddedApp) {
|
|
211
|
+
const payload = await validateSessionToken(params, request, sessionToken);
|
|
212
|
+
const dest = new URL(payload.dest);
|
|
213
|
+
const shop = dest.hostname;
|
|
214
|
+
|
|
215
|
+
logger.debug('Session token is valid - authenticated', {shop, payload});
|
|
216
|
+
const sessionId = config.useOnlineTokens
|
|
217
|
+
? api.session.getJwtSessionId(shop, payload.sub)
|
|
218
|
+
: api.session.getOfflineId(shop);
|
|
219
|
+
|
|
220
|
+
return {shop, payload, sessionId, sessionToken};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const url = new URL(request.url);
|
|
224
|
+
const shop = url.searchParams.get('shop')!;
|
|
225
|
+
|
|
226
|
+
const sessionId = await api.session.getCurrentId({
|
|
227
|
+
isOnline: config.useOnlineTokens,
|
|
228
|
+
rawRequest: request,
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
return {shop, sessionId, payload: undefined, sessionToken};
|
|
232
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import {HttpResponseError, Session} from '@shopify/shopify-api';
|
|
2
|
+
|
|
3
|
+
import type {BasicParams} from '../../../types';
|
|
4
|
+
import {redirectToAuthPage} from '../helpers';
|
|
5
|
+
import {invalidateAccessToken} from '../../helpers';
|
|
6
|
+
|
|
7
|
+
import type {CancelBillingOptions} from './types';
|
|
8
|
+
|
|
9
|
+
export function cancelBillingFactory(
|
|
10
|
+
params: BasicParams,
|
|
11
|
+
request: Request,
|
|
12
|
+
session: Session,
|
|
13
|
+
) {
|
|
14
|
+
return async function cancelBilling(options: CancelBillingOptions) {
|
|
15
|
+
const {api, logger} = params;
|
|
16
|
+
|
|
17
|
+
logger.debug('Cancelling billing', {shop: session.shop, ...options});
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
return await api.billing.cancel({
|
|
21
|
+
session,
|
|
22
|
+
subscriptionId: options.subscriptionId,
|
|
23
|
+
isTest: options.isTest,
|
|
24
|
+
prorate: options.prorate,
|
|
25
|
+
});
|
|
26
|
+
} catch (error) {
|
|
27
|
+
if (error instanceof HttpResponseError && error.response.code === 401) {
|
|
28
|
+
logger.debug('API token was invalid, redirecting to OAuth', {
|
|
29
|
+
shop: session.shop,
|
|
30
|
+
});
|
|
31
|
+
await invalidateAccessToken(params, session);
|
|
32
|
+
throw await redirectToAuthPage(params, request, session.shop);
|
|
33
|
+
} else {
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import {HttpResponseError, Session} from '@shopify/shopify-api';
|
|
2
|
+
|
|
3
|
+
import type {BasicParams} from '../../../types';
|
|
4
|
+
import {redirectToAuthPage} from '../helpers';
|
|
5
|
+
import type {AppConfigArg} from '../../../config-types';
|
|
6
|
+
import {invalidateAccessToken} from '../../helpers';
|
|
7
|
+
|
|
8
|
+
import type {CheckBillingOptions} from './types';
|
|
9
|
+
|
|
10
|
+
export function checkBillingFactory<Config extends AppConfigArg>(
|
|
11
|
+
params: BasicParams,
|
|
12
|
+
request: Request,
|
|
13
|
+
session: Session,
|
|
14
|
+
) {
|
|
15
|
+
return async function checkBilling(
|
|
16
|
+
options: CheckBillingOptions<Config> = {},
|
|
17
|
+
) {
|
|
18
|
+
const {api, logger} = params;
|
|
19
|
+
|
|
20
|
+
logger.debug('Checking billing plans', {shop: session.shop, ...options});
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
return await api.billing.check({
|
|
24
|
+
session,
|
|
25
|
+
plans: options.plans as string[],
|
|
26
|
+
isTest: options.isTest,
|
|
27
|
+
returnObject: true,
|
|
28
|
+
});
|
|
29
|
+
} catch (error) {
|
|
30
|
+
if (error instanceof HttpResponseError && error.response.code === 401) {
|
|
31
|
+
logger.debug('API token was invalid, redirecting to OAuth', {
|
|
32
|
+
shop: session.shop,
|
|
33
|
+
});
|
|
34
|
+
await invalidateAccessToken(params, session);
|
|
35
|
+
throw await redirectToAuthPage(params, request, session.shop);
|
|
36
|
+
} else {
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import {HttpResponseError, Session} from '@shopify/shopify-api';
|
|
2
|
+
|
|
3
|
+
import type {BasicParams} from '../../../types';
|
|
4
|
+
import {redirectToAuthPage} from '../helpers';
|
|
5
|
+
import {invalidateAccessToken} from '../../helpers';
|
|
6
|
+
|
|
7
|
+
import type {CreateUsageRecordOptions} from './types';
|
|
8
|
+
|
|
9
|
+
export function createUsageRecordFactory(
|
|
10
|
+
params: BasicParams,
|
|
11
|
+
request: Request,
|
|
12
|
+
session: Session,
|
|
13
|
+
) {
|
|
14
|
+
return async function createUsageRecord(options: CreateUsageRecordOptions) {
|
|
15
|
+
const {api, logger} = params;
|
|
16
|
+
|
|
17
|
+
logger.debug('Create usage record', {shop: session.shop, ...options});
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
return await api.billing.createUsageRecord({
|
|
21
|
+
...options,
|
|
22
|
+
session,
|
|
23
|
+
});
|
|
24
|
+
} catch (error) {
|
|
25
|
+
if (error instanceof HttpResponseError && error.response.code === 401) {
|
|
26
|
+
logger.debug('API token was invalid, redirecting to OAuth', {
|
|
27
|
+
shop: session.shop,
|
|
28
|
+
});
|
|
29
|
+
await invalidateAccessToken(params, session);
|
|
30
|
+
throw await redirectToAuthPage(params, request, session.shop);
|
|
31
|
+
} else {
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import {redirect} from '@remix-run/server-runtime';
|
|
2
|
+
|
|
3
|
+
import {BasicParams} from '../../../types';
|
|
4
|
+
import {getAppBridgeHeaders} from '../helpers';
|
|
5
|
+
|
|
6
|
+
export function redirectOutOfApp(
|
|
7
|
+
params: BasicParams,
|
|
8
|
+
request: Request,
|
|
9
|
+
url: string,
|
|
10
|
+
shop: string,
|
|
11
|
+
): never {
|
|
12
|
+
const {config, logger} = params;
|
|
13
|
+
|
|
14
|
+
logger.debug('Redirecting out of app', {shop, url});
|
|
15
|
+
|
|
16
|
+
const requestUrl = new URL(request.url);
|
|
17
|
+
const isEmbeddedRequest = requestUrl.searchParams.get('embedded') === '1';
|
|
18
|
+
const isXhrRequest = request.headers.get('authorization');
|
|
19
|
+
|
|
20
|
+
if (isXhrRequest) {
|
|
21
|
+
// eslint-disable-next-line no-warning-comments
|
|
22
|
+
// TODO Check this with the beta flag disabled (with the bounce page)
|
|
23
|
+
// Remix is not including the X-Shopify-API-Request-Failure-Reauthorize-Url when throwing a Response
|
|
24
|
+
// https://github.com/remix-run/remix/issues/5356
|
|
25
|
+
throw new Response(undefined, {
|
|
26
|
+
status: 401,
|
|
27
|
+
statusText: 'Unauthorized',
|
|
28
|
+
headers: getAppBridgeHeaders(url),
|
|
29
|
+
});
|
|
30
|
+
} else if (isEmbeddedRequest) {
|
|
31
|
+
const params = new URLSearchParams({
|
|
32
|
+
shop,
|
|
33
|
+
host: requestUrl.searchParams.get('host')!,
|
|
34
|
+
exitIframe: url,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
throw redirect(`${config.auth.exitIframePath}?${params.toString()}`);
|
|
38
|
+
} else {
|
|
39
|
+
// This will only ever happen for non-embedded apps, because the authenticator will stop before reaching this point
|
|
40
|
+
throw redirect(url);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export {cancelBillingFactory} from './cancel';
|
|
2
|
+
export {requireBillingFactory} from './require';
|
|
3
|
+
export {requestBillingFactory} from './request';
|
|
4
|
+
export {checkBillingFactory} from './check';
|
|
5
|
+
export {createUsageRecordFactory} from './create-usage-record';
|
|
6
|
+
export {updateUsageCappedAmountFactory} from './update-usage-subscription-capped-amount';
|