@shopify/shopify-app-remix 4.1.0 → 4.2.0

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.
Files changed (206) hide show
  1. package/dist/cjs/react/components/AppProvider/AppProvider.js.map +1 -1
  2. package/dist/cjs/react/components/AppProxyLink/AppProxyLink.js.map +1 -1
  3. package/dist/cjs/server/authenticate/admin/strategies/auth-code-flow.js.map +1 -1
  4. package/dist/cjs/server/authenticate/admin/strategies/merchant-custom-app.js.map +1 -1
  5. package/dist/cjs/server/authenticate/admin/strategies/token-exchange.js.map +1 -1
  6. package/dist/cjs/server/authenticate/webhooks/authenticate.js +34 -10
  7. package/dist/cjs/server/authenticate/webhooks/authenticate.js.map +1 -1
  8. package/dist/cjs/server/version.js +1 -1
  9. package/dist/cjs/server/version.js.map +1 -1
  10. package/dist/esm/react/components/AppProvider/AppProvider.mjs.map +1 -1
  11. package/dist/esm/react/components/AppProxyLink/AppProxyLink.mjs.map +1 -1
  12. package/dist/esm/server/authenticate/admin/strategies/auth-code-flow.mjs.map +1 -1
  13. package/dist/esm/server/authenticate/admin/strategies/merchant-custom-app.mjs.map +1 -1
  14. package/dist/esm/server/authenticate/admin/strategies/token-exchange.mjs.map +1 -1
  15. package/dist/esm/server/authenticate/webhooks/authenticate.mjs +35 -11
  16. package/dist/esm/server/authenticate/webhooks/authenticate.mjs.map +1 -1
  17. package/dist/esm/server/version.mjs +1 -1
  18. package/dist/esm/server/version.mjs.map +1 -1
  19. package/dist/ts/react/components/AppProvider/AppProvider.d.ts.map +1 -1
  20. package/dist/ts/react/components/AppProxyLink/AppProxyLink.d.ts.map +1 -1
  21. package/dist/ts/server/authenticate/admin/billing/types.d.ts.map +1 -1
  22. package/dist/ts/server/authenticate/admin/strategies/auth-code-flow.d.ts.map +1 -1
  23. package/dist/ts/server/authenticate/admin/strategies/merchant-custom-app.d.ts.map +1 -1
  24. package/dist/ts/server/authenticate/admin/strategies/token-exchange.d.ts.map +1 -1
  25. package/dist/ts/server/authenticate/admin/types.d.ts.map +1 -1
  26. package/dist/ts/server/authenticate/public/extension/types.d.ts.map +1 -1
  27. package/dist/ts/server/authenticate/webhooks/authenticate.d.ts.map +1 -1
  28. package/dist/ts/server/authenticate/webhooks/types.d.ts +34 -2
  29. package/dist/ts/server/authenticate/webhooks/types.d.ts.map +1 -1
  30. package/dist/ts/server/config-types.d.ts.map +1 -1
  31. package/dist/ts/server/version.d.ts +1 -1
  32. package/dist/tsconfig.build.tsbuildinfo +1 -1
  33. package/package.json +18 -14
  34. package/src/react/components/AppProvider/AppProvider.doc.ts +35 -0
  35. package/src/react/components/AppProvider/AppProvider.tsx +147 -0
  36. package/src/react/components/AppProvider/index.ts +1 -0
  37. package/src/react/components/AppProxyForm/AppProxyForm.doc.ts +28 -0
  38. package/src/react/components/AppProxyForm/AppProxyForm.tsx +82 -0
  39. package/src/react/components/AppProxyForm/index.ts +1 -0
  40. package/src/react/components/AppProxyLink/AppProxyLink.doc.ts +28 -0
  41. package/src/react/components/AppProxyLink/AppProxyLink.tsx +58 -0
  42. package/src/react/components/AppProxyLink/index.ts +1 -0
  43. package/src/react/components/AppProxyProvider/AppProxyProvider.doc.ts +40 -0
  44. package/src/react/components/AppProxyProvider/AppProxyProvider.tsx +100 -0
  45. package/src/react/components/AppProxyProvider/index.ts +5 -0
  46. package/src/react/components/RemixPolarisLink.tsx +14 -0
  47. package/src/react/components/index.ts +4 -0
  48. package/src/react/const.ts +2 -0
  49. package/src/react/index.ts +1 -0
  50. package/src/server/__test-helpers/const.ts +14 -0
  51. package/src/server/__test-helpers/expect-admin-api-client.ts +51 -0
  52. package/src/server/__test-helpers/expect-begin-auth-redirect.ts +22 -0
  53. package/src/server/__test-helpers/expect-document-request-headers.ts +25 -0
  54. package/src/server/__test-helpers/expect-exit-iframe.ts +37 -0
  55. package/src/server/__test-helpers/expect-login-redirect.ts +8 -0
  56. package/src/server/__test-helpers/expect-storefront-api-client.ts +49 -0
  57. package/src/server/__test-helpers/expect-token-refresh.ts +188 -0
  58. package/src/server/__test-helpers/get-hmac.ts +7 -0
  59. package/src/server/__test-helpers/get-jwt.ts +10 -0
  60. package/src/server/__test-helpers/get-thrown-error.ts +19 -0
  61. package/src/server/__test-helpers/get-thrown-response.ts +17 -0
  62. package/src/server/__test-helpers/index.ts +21 -0
  63. package/src/server/__test-helpers/mock-graphql-request.ts +60 -0
  64. package/src/server/__test-helpers/request-mock.ts +169 -0
  65. package/src/server/__test-helpers/setup-embedded-flow.ts +30 -0
  66. package/src/server/__test-helpers/setup-fetch-flow.ts +29 -0
  67. package/src/server/__test-helpers/setup-non-embedded-flow.ts +31 -0
  68. package/src/server/__test-helpers/setup-valid-request.ts +78 -0
  69. package/src/server/__test-helpers/setup-valid-session.ts +28 -0
  70. package/src/server/__test-helpers/sign-request-cookie.ts +21 -0
  71. package/src/server/__test-helpers/test-config.ts +56 -0
  72. package/src/server/adapters/node/index.ts +13 -0
  73. package/src/server/adapters/vercel/index.ts +13 -0
  74. package/src/server/authenticate/admin/authenticate.admin.doc.example.ts +34 -0
  75. package/src/server/authenticate/admin/authenticate.admin.doc.ts +51 -0
  76. package/src/server/authenticate/admin/authenticate.ts +232 -0
  77. package/src/server/authenticate/admin/billing/authenticate.admin.billing.doc.ts +29 -0
  78. package/src/server/authenticate/admin/billing/cancel.ts +38 -0
  79. package/src/server/authenticate/admin/billing/check.ts +41 -0
  80. package/src/server/authenticate/admin/billing/create-usage-record.ts +36 -0
  81. package/src/server/authenticate/admin/billing/helpers.ts +42 -0
  82. package/src/server/authenticate/admin/billing/index.ts +6 -0
  83. package/src/server/authenticate/admin/billing/request.ts +64 -0
  84. package/src/server/authenticate/admin/billing/require.ts +58 -0
  85. package/src/server/authenticate/admin/billing/types.ts +613 -0
  86. package/src/server/authenticate/admin/billing/update-usage-subscription-capped-amount.ts +55 -0
  87. package/src/server/authenticate/admin/helpers/begin-auth.ts +17 -0
  88. package/src/server/authenticate/admin/helpers/create-admin-api-context.ts +20 -0
  89. package/src/server/authenticate/admin/helpers/ensure-app-is-embedded-if-required.ts +18 -0
  90. package/src/server/authenticate/admin/helpers/ensure-session-token-search-param-if-required.ts +25 -0
  91. package/src/server/authenticate/admin/helpers/handle-client-error.ts +45 -0
  92. package/src/server/authenticate/admin/helpers/index.ts +14 -0
  93. package/src/server/authenticate/admin/helpers/redirect-to-auth-page.ts +28 -0
  94. package/src/server/authenticate/admin/helpers/redirect-to-bounce-page.ts +23 -0
  95. package/src/server/authenticate/admin/helpers/redirect-to-install-page.ts +53 -0
  96. package/src/server/authenticate/admin/helpers/redirect-to-shopify-or-app-root.ts +21 -0
  97. package/src/server/authenticate/admin/helpers/redirect-with-app-bridge-headers.ts +13 -0
  98. package/src/server/authenticate/admin/helpers/redirect-with-exitiframe.ts +28 -0
  99. package/src/server/authenticate/admin/helpers/redirect.ts +164 -0
  100. package/src/server/authenticate/admin/helpers/render-app-bridge.ts +46 -0
  101. package/src/server/authenticate/admin/helpers/trigger-after-auth-hook.ts +29 -0
  102. package/src/server/authenticate/admin/helpers/validate-redirect-url.ts +67 -0
  103. package/src/server/authenticate/admin/helpers/validate-shop-and-host-params.ts +46 -0
  104. package/src/server/authenticate/admin/scope/authenticate.admin.scopes.doc.ts +28 -0
  105. package/src/server/authenticate/admin/scope/client/fetch-scopes-details.ts +43 -0
  106. package/src/server/authenticate/admin/scope/client/revoke-scopes.ts +38 -0
  107. package/src/server/authenticate/admin/scope/factory.ts +21 -0
  108. package/src/server/authenticate/admin/scope/query.ts +51 -0
  109. package/src/server/authenticate/admin/scope/request.ts +32 -0
  110. package/src/server/authenticate/admin/scope/revoke.ts +48 -0
  111. package/src/server/authenticate/admin/scope/types.ts +126 -0
  112. package/src/server/authenticate/admin/strategies/auth-code-flow.ts +332 -0
  113. package/src/server/authenticate/admin/strategies/merchant-custom-app.ts +65 -0
  114. package/src/server/authenticate/admin/strategies/token-exchange.ts +188 -0
  115. package/src/server/authenticate/admin/strategies/types.ts +29 -0
  116. package/src/server/authenticate/admin/types.ts +216 -0
  117. package/src/server/authenticate/const.ts +15 -0
  118. package/src/server/authenticate/flow/authenticate.flow.doc.example.ts +39 -0
  119. package/src/server/authenticate/flow/authenticate.flow.doc.ts +47 -0
  120. package/src/server/authenticate/flow/authenticate.ts +68 -0
  121. package/src/server/authenticate/flow/types.ts +85 -0
  122. package/src/server/authenticate/fulfillment-service/authenticate.fulfillment-service.doc.example.ts +52 -0
  123. package/src/server/authenticate/fulfillment-service/authenticate.fulfillment-service.doc.ts +48 -0
  124. package/src/server/authenticate/fulfillment-service/authenticate.ts +82 -0
  125. package/src/server/authenticate/fulfillment-service/types.ts +82 -0
  126. package/src/server/authenticate/helpers/add-response-headers.ts +43 -0
  127. package/src/server/authenticate/helpers/app-bridge-url.ts +10 -0
  128. package/src/server/authenticate/helpers/ensure-cors-headers.ts +38 -0
  129. package/src/server/authenticate/helpers/get-session-token-header.ts +11 -0
  130. package/src/server/authenticate/helpers/get-shop-from-request.ts +4 -0
  131. package/src/server/authenticate/helpers/idempotent-promise-handler.ts +45 -0
  132. package/src/server/authenticate/helpers/index.ts +10 -0
  133. package/src/server/authenticate/helpers/invalidate-access-token.ts +17 -0
  134. package/src/server/authenticate/helpers/reject-bot-request.ts +27 -0
  135. package/src/server/authenticate/helpers/respond-to-invalid-session-token.ts +28 -0
  136. package/src/server/authenticate/helpers/respond-to-options-request.ts +26 -0
  137. package/src/server/authenticate/helpers/validate-session-token.ts +40 -0
  138. package/src/server/authenticate/login/login.ts +53 -0
  139. package/src/server/authenticate/public/appProxy/authenticate.public.app-proxy.doc.example.ts +27 -0
  140. package/src/server/authenticate/public/appProxy/authenticate.public.app-proxy.doc.ts +55 -0
  141. package/src/server/authenticate/public/appProxy/authenticate.ts +150 -0
  142. package/src/server/authenticate/public/appProxy/types.ts +215 -0
  143. package/src/server/authenticate/public/checkout/authenticate.public.checkout.doc.example.offers.ts +21 -0
  144. package/src/server/authenticate/public/checkout/authenticate.public.checkout.doc.example.ts +17 -0
  145. package/src/server/authenticate/public/checkout/authenticate.public.checkout.doc.ts +49 -0
  146. package/src/server/authenticate/public/checkout/authenticate.ts +13 -0
  147. package/src/server/authenticate/public/checkout/types.ts +65 -0
  148. package/src/server/authenticate/public/customer-account/authenticate.public.customerAccount.doc.example.offers.ts +36 -0
  149. package/src/server/authenticate/public/customer-account/authenticate.public.customerAccount.doc.example.ts +18 -0
  150. package/src/server/authenticate/public/customer-account/authenticate.public.customerAccount.doc.ts +49 -0
  151. package/src/server/authenticate/public/customer-account/authenticate.ts +13 -0
  152. package/src/server/authenticate/public/customer-account/types.ts +65 -0
  153. package/src/server/authenticate/public/extension/authenticate.ts +54 -0
  154. package/src/server/authenticate/public/extension/types.ts +11 -0
  155. package/src/server/authenticate/public/factory.ts +24 -0
  156. package/src/server/authenticate/public/index.ts +1 -0
  157. package/src/server/authenticate/public/pos/authenticate.ts +8 -0
  158. package/src/server/authenticate/public/pos/types.ts +65 -0
  159. package/src/server/authenticate/public/types.ts +96 -0
  160. package/src/server/authenticate/webhooks/authenticate.ts +105 -0
  161. package/src/server/authenticate/webhooks/authenticate.webhooks.doc.example.ts +43 -0
  162. package/src/server/authenticate/webhooks/authenticate.webhooks.doc.ts +39 -0
  163. package/src/server/authenticate/webhooks/index.ts +1 -0
  164. package/src/server/authenticate/webhooks/register.ts +48 -0
  165. package/src/server/authenticate/webhooks/types.ts +266 -0
  166. package/src/server/boundary/error.tsx +14 -0
  167. package/src/server/boundary/headers.ts +15 -0
  168. package/src/server/boundary/index.ts +39 -0
  169. package/src/server/boundary/types.ts +4 -0
  170. package/src/server/clients/admin/authenticate.admin.api.doc.ts +34 -0
  171. package/src/server/clients/admin/factory.ts +22 -0
  172. package/src/server/clients/admin/graphql.ts +39 -0
  173. package/src/server/clients/admin/index.ts +2 -0
  174. package/src/server/clients/admin/types.ts +120 -0
  175. package/src/server/clients/index.ts +2 -0
  176. package/src/server/clients/storefront/authenticate.storefront.api.doc.ts +34 -0
  177. package/src/server/clients/storefront/factory.ts +32 -0
  178. package/src/server/clients/storefront/index.ts +2 -0
  179. package/src/server/clients/storefront/types.ts +78 -0
  180. package/src/server/clients/types.ts +47 -0
  181. package/src/server/config-types.ts +309 -0
  182. package/src/server/errors.ts +3 -0
  183. package/src/server/future/flags.ts +62 -0
  184. package/src/server/helpers/create-or-load-offline-session.ts +19 -0
  185. package/src/server/helpers/ensure-offline-token-is-not-expired.ts +32 -0
  186. package/src/server/helpers/ensure-valid-offline-session.ts +15 -0
  187. package/src/server/helpers/index.ts +4 -0
  188. package/src/server/helpers/refresh-token.ts +35 -0
  189. package/src/server/index.ts +24 -0
  190. package/src/server/override-logger.ts +42 -0
  191. package/src/server/shopify-app.doc.ts +45 -0
  192. package/src/server/shopify-app.ts +215 -0
  193. package/src/server/test-helpers/index.ts +1 -0
  194. package/src/server/test-helpers/test-config.ts +79 -0
  195. package/src/server/types-contexts.ts +44 -0
  196. package/src/server/types.ts +507 -0
  197. package/src/server/unauthenticated/admin/factory.ts +23 -0
  198. package/src/server/unauthenticated/admin/index.ts +1 -0
  199. package/src/server/unauthenticated/admin/types.ts +79 -0
  200. package/src/server/unauthenticated/admin/unauthenticated.admin.doc.ts +40 -0
  201. package/src/server/unauthenticated/storefront/factory.ts +28 -0
  202. package/src/server/unauthenticated/storefront/index.ts +1 -0
  203. package/src/server/unauthenticated/storefront/types.ts +105 -0
  204. package/src/server/unauthenticated/storefront/unauthenticated.storefront.doc.ts +40 -0
  205. package/src/server/unauthenticated/types.ts +65 -0
  206. package/src/server/version.ts +1 -0
@@ -0,0 +1,78 @@
1
+ import {StorefrontOperations} from '@shopify/storefront-api-client';
2
+
3
+ import {GraphQLClient} from '../types';
4
+
5
+ export interface StorefrontContext {
6
+ /**
7
+ * Method for interacting with the Shopify Storefront GraphQL API
8
+ *
9
+ * 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).
10
+ *
11
+ * {@link https://shopify.dev/docs/api/storefront}
12
+ *
13
+ * @example
14
+ * <caption>Querying the GraphQL API.</caption>
15
+ * <description>Use `storefront.graphql` to make query / mutation requests.</description>
16
+ * ```ts
17
+ * // app/routes/**\/.ts
18
+ * import { json } from "@remix-run/node";
19
+ * import { authenticate } from "../shopify.server";
20
+ *
21
+ * export async function action({ request }: ActionFunctionArgs) {
22
+ * const { storefront } = await authenticate.public.appProxy(request);
23
+ *
24
+ * const response = await storefront.graphql(`{blogs(first: 10) { edges { node { id } } } }`);
25
+ *
26
+ * return json(await response.json());
27
+ * }
28
+ * ```
29
+ *
30
+ * @example
31
+ * <caption>Handling GraphQL errors.</caption>
32
+ * <description>Catch `GraphqlQueryError` errors to see error messages from the API.</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 { storefront } = await authenticate.public.appProxy(request);
40
+ *
41
+ * try {
42
+ * const response = await storefront.graphql(
43
+ * `#graphql
44
+ * query incorrectQuery {
45
+ * products(first: 10) {
46
+ * nodes {
47
+ * not_a_field
48
+ * }
49
+ * }
50
+ * }`,
51
+ * );
52
+ *
53
+ * return json({ data: await response.json() });
54
+ * } catch (error) {
55
+ * if (error instanceof GraphqlQueryError) {
56
+ * // { errors: { graphQLErrors: [
57
+ * // { message: "Field 'not_a_field' doesn't exist on type 'Product'" }
58
+ * // ] } }
59
+ * return json({ errors: error.body?.errors }, { status: 500 });
60
+ * }
61
+ * return json({ message: "An error occurred" }, { status: 500 });
62
+ * }
63
+ * }
64
+ * ```
65
+ *
66
+ * ```ts
67
+ * // /app/shopify.server.ts
68
+ * import { shopifyApp } from "@shopify/shopify-app-remix/server";
69
+ *
70
+ * const shopify = shopifyApp({
71
+ * // ...
72
+ * });
73
+ * export default shopify;
74
+ * export const authenticate = shopify.authenticate;
75
+ * ```
76
+ */
77
+ graphql: GraphQLClient<StorefrontOperations>;
78
+ }
@@ -0,0 +1,47 @@
1
+ import type {
2
+ AllOperations,
3
+ ReturnData,
4
+ FetchResponseBody,
5
+ ApiClientRequestOptions,
6
+ ResponseWithType,
7
+ } from '@shopify/admin-api-client';
8
+ import {ApiVersion} from '@shopify/shopify-api';
9
+
10
+ export interface GraphQLQueryOptions<
11
+ Operation extends keyof Operations,
12
+ Operations extends AllOperations,
13
+ > {
14
+ /**
15
+ * The variables to pass to the operation.
16
+ */
17
+ variables?: ApiClientRequestOptions<Operation, Operations>['variables'];
18
+ /**
19
+ * The version of the API to use for the request.
20
+ */
21
+ apiVersion?: ApiVersion;
22
+ /**
23
+ * Additional headers to include in the request.
24
+ */
25
+ headers?: Record<string, any>;
26
+ /**
27
+ * The total number of times to try the request if it fails.
28
+ */
29
+ tries?: number;
30
+
31
+ /**
32
+ * An optional AbortSignal to cancel the request.
33
+ */
34
+ signal?: AbortSignal;
35
+ }
36
+
37
+ export type GraphQLResponse<
38
+ Operation extends keyof Operations,
39
+ Operations extends AllOperations,
40
+ > = ResponseWithType<FetchResponseBody<ReturnData<Operation, Operations>>>;
41
+
42
+ export type GraphQLClient<Operations extends AllOperations> = <
43
+ Operation extends keyof Operations,
44
+ >(
45
+ query: Operation,
46
+ options?: GraphQLQueryOptions<Operation, Operations>,
47
+ ) => Promise<GraphQLResponse<Operation, Operations>>;
@@ -0,0 +1,309 @@
1
+ import {
2
+ ConfigParams as ApiConfigArg,
3
+ ConfigInterface as ApiConfig,
4
+ Session,
5
+ ApiVersion,
6
+ WebhookHandler,
7
+ } from '@shopify/shopify-api';
8
+ import {SessionStorage} from '@shopify/shopify-app-session-storage';
9
+
10
+ import type {
11
+ ApiFutureFlags,
12
+ FutureFlagOptions,
13
+ FutureFlags,
14
+ } from './future/flags';
15
+ import type {AppDistribution} from './types';
16
+ import type {AdminApiContext} from './clients';
17
+ import {IdempotentPromiseHandler} from './authenticate/helpers/idempotent-promise-handler';
18
+
19
+ export interface AppConfigArg<
20
+ Storage extends SessionStorage = SessionStorage,
21
+ Future extends FutureFlagOptions = FutureFlagOptions,
22
+ > extends Omit<
23
+ ApiConfigArg<ApiFutureFlags<Future>>,
24
+ | 'hostName'
25
+ | 'hostScheme'
26
+ | 'isEmbeddedApp'
27
+ | 'apiVersion'
28
+ | 'isCustomStoreApp'
29
+ | 'future'
30
+ > {
31
+ /**
32
+ * The URL your app is running on.
33
+ *
34
+ * The `@shopify/cli` provides this URL as `process.env.SHOPIFY_APP_URL`. For development this is probably a tunnel URL that points to your local machine. If this is a production app, this is your production URL.
35
+ */
36
+ appUrl: string;
37
+
38
+ /**
39
+ * An adaptor for storing sessions in your database of choice.
40
+ *
41
+ * Shopify provides multiple session storage adaptors and you can create your own.
42
+ *
43
+ * Optional for apps created in the Shopify Admin.
44
+ *
45
+ * {@link https://github.com/Shopify/shopify-app-js/blob/main/README.md#session-storage-options}
46
+ *
47
+ * @example
48
+ * <caption>Storing sessions with Prisma.</caption>
49
+ * <description>Add the `@shopify/shopify-app-session-storage-prisma` package to use the Prisma session storage.</description>
50
+ * ```ts
51
+ * import { shopifyApp } from "@shopify/shopify-app-remix/server";
52
+ * import { PrismaSessionStorage } from "@shopify/shopify-app-session-storage-prisma";
53
+ *
54
+ * import prisma from "~/db.server";
55
+ *
56
+ * const shopify = shopifyApp({
57
+ * // ... etc
58
+ * sessionStorage: new PrismaSessionStorage(prisma),
59
+ * });
60
+ * export default shopify;
61
+ * ```
62
+ */
63
+ sessionStorage?: Storage;
64
+
65
+ /**
66
+ * Whether your app use online or offline tokens.
67
+ *
68
+ * If your app uses online tokens, then both online and offline tokens will be saved to your database. This ensures your app can perform background jobs.
69
+ *
70
+ * {@link https://shopify.dev/docs/apps/auth/oauth/access-modes}
71
+ *
72
+ * @defaultValue `false`
73
+ */
74
+ useOnlineTokens?: boolean;
75
+
76
+ /**
77
+ * The config for the shop-specific webhooks your app needs.
78
+ *
79
+ * Use this to configure shop-specific webhooks. In many cases defining app-specific webhooks in the `shopify.app.toml` will be sufficient and easier to manage. Please see:
80
+ *
81
+ * {@link https://shopify.dev/docs/apps/build/webhooks/subscribe#app-specific-vs-shop-specific-subscriptions}
82
+ *
83
+ * You should only use this if you need shop-specific webhooks. If you do need shop-specific webhooks this can be in used in conjunction with the afterAuth hook, loaders or processes such as background jobs.
84
+ *
85
+ * @example
86
+ * <caption>Registering shop-specific webhooks.</caption>
87
+ * ```ts
88
+ * // app/shopify.server.ts
89
+ * import { DeliveryMethod, shopifyApp } from "@shopify/shopify-app-remix/server";
90
+ *
91
+ * const shopify = shopifyApp({
92
+ * webhooks: {
93
+ * PRODUCTS_CREATE: {
94
+ * deliveryMethod: DeliveryMethod.Http,
95
+ * callbackUrl: "/webhooks/products/create",
96
+ * },
97
+ * },
98
+ * hooks: {
99
+ * afterAuth: async ({ session }) => {
100
+ * // Register webhooks for the shop
101
+ * // In this example, every shop will have these webhooks
102
+ * // But you could wrap this in some custom shop specific conditional logic
103
+ * shopify.registerWebhooks({ session });
104
+ * }
105
+ * },
106
+ * // ...etc
107
+ * });
108
+ * export default shopify;
109
+ * export const authenticate = shopify.authenticate;
110
+ * ```
111
+ *
112
+ * @example
113
+ * <caption>Registering app-specific webhooks (Recommended)</caption>
114
+ * ```toml
115
+ * # shopify.app.toml
116
+ * [webhooks]
117
+ * api_version = "2024-07"
118
+
119
+ * [[webhooks.subscriptions]]
120
+ * topics = ["products/create"]
121
+ * uri = "/webhooks/products/create"
122
+ *
123
+ * ```
124
+ *
125
+ * @example
126
+ * <caption>Authenticating a webhook request</caption>
127
+ *
128
+ * ```ts
129
+ * // /app/routes/webhooks.ts
130
+ * import { ActionFunctionArgs } from "@remix-run/node";
131
+ * import { authenticate } from "../shopify.server";
132
+ * import db from "../db.server";
133
+ *
134
+ * export const action = async ({ request }: ActionFunctionArgs) => {
135
+ * const { topic, shop, session, payload } = await authenticate.webhook(request);
136
+ *
137
+ * // Webhook requests can trigger after an app is uninstalled
138
+ * // If the app is already uninstalled, the session may be undefined.
139
+ * if (!session) {
140
+ * throw new Response();
141
+ * }
142
+ *
143
+ * // Handle the webhook
144
+ * console.log(`${TOPIC} webhook received with`, JSON.stringify(payload))
145
+ *
146
+ * throw new Response();
147
+ * };
148
+ * ```
149
+ */
150
+ webhooks?: WebhookConfig;
151
+
152
+ /**
153
+ * Functions to call at key places during your apps lifecycle.
154
+ *
155
+ * These functions are called in the context of the request that triggered them. This means you can access the session.
156
+ *
157
+ * @example
158
+ * <caption>Seeding your database custom data when a merchant installs your app.</caption>
159
+ * ```ts
160
+ * import { DeliveryMethod, shopifyApp } from "@shopify/shopify-app-remix/server";
161
+ * import { seedStoreData } from "~/db/seeds"
162
+ *
163
+ * const shopify = shopifyApp({
164
+ * hooks: {
165
+ * afterAuth: async ({ session }) => {
166
+ * seedStoreData({session})
167
+ * }
168
+ * },
169
+ * // ...etc
170
+ * });
171
+ * ```
172
+ */
173
+ hooks?: HooksConfig;
174
+
175
+ /**
176
+ * Does your app render embedded inside the Shopify Admin or on its own.
177
+ *
178
+ * Unless you have very specific needs, this should be true.
179
+ *
180
+ * @defaultValue `true`
181
+ */
182
+ isEmbeddedApp?: boolean;
183
+
184
+ /**
185
+ * How your app is distributed. Default is `AppDistribution.AppStore`.
186
+ *
187
+ * AppStore should be used for public apps that are distributed in the Shopify App Store.
188
+ * SingleMerchant should be used for custom apps managed in the Partner Dashboard.
189
+ * ShopifyAdmin should be used for apps that are managed in the merchant's Shopify Admin.
190
+ *
191
+ * {@link https://shopify.dev/docs/apps/distribution}
192
+ */
193
+ distribution?: AppDistribution;
194
+
195
+ /**
196
+ * What version of Shopify's Admin API's would you like to use.
197
+ *
198
+ * {@link https://shopify.dev/docs/api/}
199
+ *
200
+ * @example
201
+ * <caption>Using a specific API Version</caption>
202
+ * ```ts
203
+ * import { ApiVersion, shopifyApp } from "@shopify/shopify-app-remix/server";
204
+ *
205
+ * const shopify = shopifyApp({
206
+ * // ...etc
207
+ * apiVersion: ApiVersion.July25,
208
+ * });
209
+ * ```
210
+ */
211
+ apiVersion: ApiVersion;
212
+
213
+ /**
214
+ * A path that Shopify can reserve for auth related endpoints.
215
+ *
216
+ * This must match a $ route in your Remix app. That route must export a loader function that calls `shopify.authenticate.admin(request)`.
217
+ *
218
+ * @default `"/auth"`
219
+ *
220
+ * @example
221
+ * <caption>Using an authPathPrefix</caption>
222
+ * ```ts
223
+ * // /app/shopify.server.ts
224
+ * import { ApiVersion, shopifyApp } from "@shopify/shopify-app-remix/server";
225
+ *
226
+ * const shopify = shopifyApp({
227
+ * // ...etc
228
+ * authPathPrefix: "/auth",
229
+ * });
230
+ * export default shopify;
231
+ * export const authenticate = shopify.authenticate;
232
+ *
233
+ * // /app/routes/auth/$.jsx
234
+ * import { LoaderFunctionArgs } from "@remix-run/node";
235
+ * import { authenticate } from "../../shopify.server";
236
+ *
237
+ * export async function loader({ request }: LoaderFunctionArgs) {
238
+ * await authenticate.admin(request);
239
+ *
240
+ * return null
241
+ * }
242
+ * ```
243
+ */
244
+ authPathPrefix?: string;
245
+
246
+ /**
247
+ * Features that will be introduced in future releases of this package.
248
+ *
249
+ * You can opt in to these features by setting the corresponding flags. By doing so, you can prepare for future
250
+ * releases in advance and provide feedback on the new features.
251
+ */
252
+ future?: Future;
253
+ }
254
+
255
+ export interface AppConfig<
256
+ Storage extends SessionStorage = SessionStorage,
257
+ > extends Omit<ApiConfig, 'future'> {
258
+ canUseLoginForm: boolean;
259
+ appUrl: string;
260
+ auth: AuthConfig;
261
+ sessionStorage?: Storage;
262
+ useOnlineTokens: boolean;
263
+ hooks: HooksConfig;
264
+ future: FutureFlags;
265
+ idempotentPromiseHandler: IdempotentPromiseHandler;
266
+ distribution: AppDistribution;
267
+ }
268
+
269
+ export interface AuthConfig {
270
+ path: string;
271
+ callbackPath: string;
272
+ exitIframePath: string;
273
+ patchSessionTokenPath: string;
274
+ loginPath: string;
275
+ }
276
+
277
+ export type WebhookConfig = Record<string, WebhookHandler | WebhookHandler[]>;
278
+
279
+ interface HooksConfig {
280
+ /**
281
+ * A function to call after a merchant installs your app
282
+ *
283
+ * @param context - An object with context about the request that triggered the hook.
284
+ * @param context.session - The session of the merchant that installed your app. This is the output of sessionStorage.loadSession in case people want to load their own.
285
+ * @param context.admin - An object with access to the Shopify Admin API's.
286
+ *
287
+ * @example
288
+ * <caption>Seeding data when a merchant installs your app.</caption>
289
+ * ```ts
290
+ * import { DeliveryMethod, shopifyApp } from "@shopify/shopify-app-remix/server";
291
+ * import { seedStoreData } from "~/db/seeds"
292
+ *
293
+ * const shopify = shopifyApp({
294
+ * hooks: {
295
+ * afterAuth: async ({ session }) => {
296
+ * seedStoreData({session})
297
+ * }
298
+ * },
299
+ * // ...etc
300
+ * });
301
+ * ```
302
+ */
303
+ afterAuth?: (options: AfterAuthOptions) => void | Promise<void>;
304
+ }
305
+
306
+ export interface AfterAuthOptions {
307
+ session: Session;
308
+ admin: AdminApiContext;
309
+ }
@@ -0,0 +1,3 @@
1
+ import {ShopifyError} from '@shopify/shopify-api';
2
+
3
+ export class SessionNotFoundError extends ShopifyError {}
@@ -0,0 +1,62 @@
1
+ import type {ConfigParams, Shopify} from '@shopify/shopify-api';
2
+
3
+ import {AppConfig} from '../config-types';
4
+
5
+ // When adding new flags, you should also add them to the `TEST_FUTURE_FLAGS` object in `test-config.ts` to ensure that
6
+ // it doesn't cause regressions.
7
+ export interface FutureFlags {
8
+ /**
9
+ * When enabled, embedded apps will fetch access tokens via [token exchange](https://shopify.dev/docs/apps/auth/get-access-tokens/token-exchange).
10
+ * This assumes the app has scopes declared for [Shopify managing installation](https://shopify.dev/docs/apps/auth/installation#shopify-managed-installation).
11
+ *
12
+ * Learn more about this [new embedded app auth strategy](https://shopify.dev/docs/api/shopify-app-remix#embedded-auth-strategy).
13
+ *
14
+ * @default false
15
+ */
16
+ unstable_newEmbeddedAuthStrategy?: boolean;
17
+ /**
18
+ * When enabled, the app will start using expiring offline access tokens and automatically refresh them when they are close to expiring.
19
+ *
20
+ * @default false
21
+ */
22
+ expiringOfflineAccessTokens?: boolean;
23
+ }
24
+
25
+ // When adding new flags, use this format:
26
+ // apiFutureFlag: Future extends FutureFlags ? Future['remixFutureFlag'] : false;
27
+ export interface ApiFutureFlags<Future extends FutureFlagOptions> {
28
+ expiringOfflineAccessTokens: Future extends FutureFlags
29
+ ? Future['expiringOfflineAccessTokens']
30
+ : false;
31
+ unstable_managedPricingSupport: true;
32
+ }
33
+
34
+ export type ApiConfigWithFutureFlags<Future extends FutureFlagOptions> =
35
+ ConfigParams<ApiFutureFlags<Future>>;
36
+
37
+ export type FutureFlagOptions = FutureFlags | undefined;
38
+
39
+ export type FeatureEnabled<
40
+ Future extends FutureFlagOptions,
41
+ Flag extends keyof FutureFlags,
42
+ > = Future extends FutureFlags
43
+ ? Future[Flag] extends true
44
+ ? true
45
+ : false
46
+ : false;
47
+
48
+ export function logDisabledFutureFlags(
49
+ config: AppConfig,
50
+ logger: Shopify['logger'],
51
+ ) {
52
+ const logFlag = (flag: string, message: string) =>
53
+ logger.info(`Future flag ${flag} is disabled.\n\n ${message}\n`);
54
+
55
+ if (!config.future.unstable_newEmbeddedAuthStrategy) {
56
+ logFlag(
57
+ 'unstable_newEmbeddedAuthStrategy',
58
+ 'Enable this to use OAuth token exchange instead of auth code to generate API access tokens.' +
59
+ '\n Your app must be using Shopify managed install: https://shopify.dev/docs/apps/auth/installation',
60
+ );
61
+ }
62
+ }
@@ -0,0 +1,19 @@
1
+ import {AppDistribution, BasicParams} from '../types';
2
+
3
+ export async function createOrLoadOfflineSession(
4
+ {api, config, logger}: BasicParams,
5
+ shop: string,
6
+ ) {
7
+ if (config.distribution === AppDistribution.ShopifyAdmin) {
8
+ logger.debug('Creating custom app session from configured access token', {
9
+ shop,
10
+ });
11
+ return api.session.customAppSession(shop);
12
+ } else {
13
+ logger.debug('Loading offline session from session storage', {shop});
14
+ const offlineSessionId = api.session.getOfflineId(shop);
15
+ const session = await config.sessionStorage!.loadSession(offlineSessionId);
16
+
17
+ return session;
18
+ }
19
+ }
@@ -0,0 +1,32 @@
1
+ import {Session} from '@shopify/shopify-api';
2
+
3
+ import {AppDistribution, BasicParams} from '../types';
4
+
5
+ import refreshToken from './refresh-token';
6
+
7
+ // 5 minutes
8
+ export const WITHIN_MILLISECONDS_OF_EXPIRY = 5 * 60 * 1000;
9
+
10
+ export async function ensureOfflineTokenIsNotExpired(
11
+ session: Session,
12
+ params: BasicParams,
13
+ shop: string,
14
+ ) {
15
+ const {config} = params;
16
+ if (
17
+ config.future?.expiringOfflineAccessTokens &&
18
+ session.isExpired(WITHIN_MILLISECONDS_OF_EXPIRY) &&
19
+ config.distribution !== AppDistribution.ShopifyAdmin &&
20
+ session.refreshToken
21
+ ) {
22
+ const offlineSession = await refreshToken(
23
+ params,
24
+ shop,
25
+ session.refreshToken,
26
+ );
27
+
28
+ await config.sessionStorage!.storeSession(offlineSession);
29
+ return offlineSession;
30
+ }
31
+ return session;
32
+ }
@@ -0,0 +1,15 @@
1
+ import {BasicParams} from '../types';
2
+
3
+ import {createOrLoadOfflineSession} from './create-or-load-offline-session';
4
+ import {ensureOfflineTokenIsNotExpired} from './ensure-offline-token-is-not-expired';
5
+
6
+ export async function ensureValidOfflineSession(
7
+ params: BasicParams,
8
+ shop: string,
9
+ ) {
10
+ const session = await createOrLoadOfflineSession(params, shop);
11
+
12
+ if (!session) return undefined;
13
+
14
+ return ensureOfflineTokenIsNotExpired(session, params, shop);
15
+ }
@@ -0,0 +1,4 @@
1
+ export * from './ensure-valid-offline-session';
2
+ export * from './ensure-offline-token-is-not-expired';
3
+ export * from './create-or-load-offline-session';
4
+ export * from './refresh-token';
@@ -0,0 +1,35 @@
1
+ import {
2
+ HttpResponseError,
3
+ InvalidJwtError,
4
+ Session,
5
+ } from '@shopify/shopify-api';
6
+
7
+ import {BasicParams} from '../types';
8
+
9
+ export default async function refreshToken(
10
+ params: BasicParams,
11
+ shop: string,
12
+ refreshToken: string,
13
+ ): Promise<Session> {
14
+ const {api} = params;
15
+ try {
16
+ const {session} = await api.auth.refreshToken({
17
+ shop,
18
+ refreshToken,
19
+ });
20
+ return session;
21
+ } catch (error) {
22
+ if (
23
+ error instanceof InvalidJwtError ||
24
+ (error instanceof HttpResponseError &&
25
+ error.response.code === 400 &&
26
+ error.response.body?.error === 'invalid_subject_token')
27
+ ) {
28
+ throw error;
29
+ }
30
+ throw new Response(undefined, {
31
+ status: 500,
32
+ statusText: 'Internal Server Error',
33
+ });
34
+ }
35
+ }
@@ -0,0 +1,24 @@
1
+ import '@shopify/shopify-api/adapters/web-api';
2
+ import {setAbstractRuntimeString} from '@shopify/shopify-api/runtime';
3
+
4
+ setAbstractRuntimeString(() => {
5
+ return `Remix`;
6
+ });
7
+
8
+ export {
9
+ LogSeverity,
10
+ DeliveryMethod,
11
+ BillingInterval,
12
+ BillingReplacementBehavior,
13
+ ApiVersion,
14
+ Session,
15
+ } from '@shopify/shopify-api';
16
+
17
+ export type {JwtPayload} from '@shopify/shopify-api';
18
+
19
+ export type * from './types-contexts';
20
+ export type {ShopifyApp, LoginError} from './types';
21
+ export {LoginErrorType, AppDistribution} from './types';
22
+ export {boundary} from './boundary';
23
+ export {shopifyApp} from './shopify-app';
24
+ export * from './errors';
@@ -0,0 +1,42 @@
1
+ import {compare} from 'compare-versions';
2
+ import {FeatureDeprecatedError, Shopify} from '@shopify/shopify-api';
3
+
4
+ import {SHOPIFY_REMIX_LIBRARY_VERSION} from './version';
5
+
6
+ // eslint-disable-next-line no-warning-comments
7
+ // TODO This has been copied from shopify-app-express, it should be extracted into a shared package
8
+ // https://github.com/orgs/Shopify/projects/6899/views/1?pane=issue&itemId=28358070
9
+ export function overrideLogger(logger: Shopify['logger']): Shopify['logger'] {
10
+ const baseContext = {package: 'shopify-app'};
11
+
12
+ const warningFunction: Shopify['logger']['warning'] = (
13
+ message,
14
+ context = {},
15
+ ) => logger.warning(message, {...baseContext, ...context});
16
+
17
+ function deprecated(warningFunction: Shopify['logger']['warning']) {
18
+ return function (version: string, message: string): Promise<void> {
19
+ if (compare(SHOPIFY_REMIX_LIBRARY_VERSION, version, '>=')) {
20
+ throw new FeatureDeprecatedError(
21
+ `Feature was deprecated in version ${version}`,
22
+ );
23
+ }
24
+
25
+ return warningFunction(`[Deprecated | ${version}] ${message}`);
26
+ };
27
+ }
28
+
29
+ return {
30
+ ...logger,
31
+ log: (severity, message, context = {}) =>
32
+ logger.log(severity, message, {...baseContext, ...context}),
33
+ debug: (message, context = {}) =>
34
+ logger.debug(message, {...baseContext, ...context}),
35
+ info: (message, context = {}) =>
36
+ logger.info(message, {...baseContext, ...context}),
37
+ warning: warningFunction,
38
+ error: (message, context = {}) =>
39
+ logger.error(message, {...baseContext, ...context}),
40
+ deprecated: deprecated(warningFunction),
41
+ };
42
+ }