@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,507 @@
1
+ import {RegisterReturn, Shopify} from '@shopify/shopify-api';
2
+ import {SessionStorage} from '@shopify/shopify-app-session-storage';
3
+
4
+ import type {AuthenticateAdmin} from './authenticate/admin/types';
5
+ import type {AuthenticateFlow} from './authenticate/flow/types';
6
+ import {AuthenticateFulfillmentService} from './authenticate/fulfillment-service/types';
7
+ import type {AuthenticatePublic} from './authenticate/public/types';
8
+ import type {
9
+ AuthenticateWebhook,
10
+ RegisterWebhooksOptions,
11
+ } from './authenticate/webhooks/types';
12
+ import type {AppConfig, AppConfigArg} from './config-types';
13
+ import type {
14
+ ApiConfigWithFutureFlags,
15
+ ApiFutureFlags,
16
+ FutureFlagOptions,
17
+ } from './future/flags';
18
+ import type {Unauthenticated} from './unauthenticated/types';
19
+
20
+ export interface BasicParams<
21
+ Future extends FutureFlagOptions = FutureFlagOptions,
22
+ > {
23
+ api: Shopify<ApiConfigWithFutureFlags<Future>, ApiFutureFlags<Future>>;
24
+ config: AppConfig;
25
+ logger: Shopify['logger'];
26
+ }
27
+
28
+ // eslint-disable-next-line no-warning-comments
29
+ // TODO: Use this enum to replace the isCustomStoreApp config option in shopify-api-js
30
+ export enum AppDistribution {
31
+ AppStore = 'app_store',
32
+ SingleMerchant = 'single_merchant',
33
+ ShopifyAdmin = 'shopify_admin',
34
+ }
35
+
36
+ type RegisterWebhooks = (
37
+ options: RegisterWebhooksOptions,
38
+ ) => Promise<RegisterReturn | void>;
39
+
40
+ export enum LoginErrorType {
41
+ MissingShop = 'MISSING_SHOP',
42
+ InvalidShop = 'INVALID_SHOP',
43
+ }
44
+
45
+ export interface LoginError {
46
+ shop?: LoginErrorType;
47
+ }
48
+
49
+ type Login = (request: Request) => Promise<LoginError | never>;
50
+
51
+ type AddDocumentResponseHeaders = (request: Request, headers: Headers) => void;
52
+
53
+ type SessionStorageType<Config extends AppConfigArg> =
54
+ Config['sessionStorage'] extends SessionStorage
55
+ ? Config['sessionStorage']
56
+ : SessionStorage;
57
+
58
+ interface Authenticate<Config extends AppConfigArg> {
59
+ /**
60
+ * Authenticate an admin Request and get back an authenticated admin context. Use the authenticated admin context to interact with Shopify.
61
+ *
62
+ * Examples of when to use this are requests from your app's UI, or requests from admin extensions.
63
+ *
64
+ * If there is no session for the Request, this will redirect the merchant to correct auth flows.
65
+ *
66
+ * @example
67
+ * <caption>Authenticating a request for an embedded app.</caption>
68
+ * ```ts
69
+ * // /app/routes/**\/*.jsx
70
+ * import { LoaderFunctionArgs, json } from "@remix-run/node";
71
+ * import { authenticate } from "../../shopify.server";
72
+ *
73
+ * export async function loader({ request }: LoaderFunctionArgs) {
74
+ * const {admin, session, sessionToken, billing} = authenticate.admin(request);
75
+ * const response = await admin.graphql(`{ shop { name } }`)
76
+ *
77
+ * return json(await response.json());
78
+ * }
79
+ * ```
80
+ * ```ts
81
+ * // /app/shopify.server.ts
82
+ * import { ApiVersion, shopifyApp } from "@shopify/shopify-app-remix/server";
83
+ *
84
+ * const shopify = shopifyApp({
85
+ * // ...etc
86
+ * });
87
+ * export default shopify;
88
+ * export const authenticate = shopify.authenticate;
89
+ * ```
90
+ */
91
+ admin: AuthenticateAdmin<Config>;
92
+
93
+ /**
94
+ * Authenticate a Flow extension Request and get back an authenticated context, containing an admin context to access
95
+ * the API, and the payload of the request.
96
+ *
97
+ * If there is no session for the Request, this will return an HTTP 400 error.
98
+ *
99
+ * Note that this will always be a POST request.
100
+ *
101
+ * @example
102
+ * <caption>Authenticating a Flow extension request.</caption>
103
+ * ```ts
104
+ * // /app/routes/**\/*.jsx
105
+ * import { ActionFunctionArgs, json } from "@remix-run/node";
106
+ * import { authenticate } from "../../shopify.server";
107
+ *
108
+ * export async function action({ request }: ActionFunctionArgs) {
109
+ * const {admin, session, payload} = authenticate.flow(request);
110
+ *
111
+ * // Perform flow extension logic
112
+ *
113
+ * // Return a 200 response
114
+ * return null;
115
+ * }
116
+ * ```
117
+ * ```ts
118
+ * // /app/shopify.server.ts
119
+ * import { ApiVersion, shopifyApp } from "@shopify/shopify-app-remix/server";
120
+ *
121
+ * const shopify = shopifyApp({
122
+ * // ...etc
123
+ * });
124
+ * export default shopify;
125
+ * export const authenticate = shopify.authenticate;
126
+ * ```
127
+ */
128
+ flow: AuthenticateFlow;
129
+
130
+ /**
131
+ * Authenticate a request from a fulfillment service and get back an authenticated context.
132
+ *
133
+ * @example
134
+ * <caption>Shopify session for the fulfillment service request.</caption>
135
+ * <description>Use the session associated with this request to use the Admin GraphQL API </description>
136
+ * ```ts
137
+ * // /app/routes/fulfillment_order_notification.ts
138
+ * import { ActionFunctionArgs } from "@remix-run/node";
139
+ * import { authenticate } from "../shopify.server";
140
+ *
141
+ * export async function action({ request }: ActionFunctionArgs) {
142
+ * const { admin, session } = await authenticate.fulfillmentService(request);
143
+ *
144
+ * console.log(session.id)
145
+ *
146
+ * return new Response();
147
+ * }
148
+ * ```
149
+ * */
150
+ fulfillmentService: AuthenticateFulfillmentService;
151
+
152
+ /**
153
+ * Authenticate a public request and get back a session token.
154
+ *
155
+ * @example
156
+ * <caption>Authenticating a request from a checkout extension</caption>
157
+ *
158
+ * ```ts
159
+ * // /app/routes/api/checkout.jsx
160
+ * import { LoaderFunctionArgs, json } from "@remix-run/node";
161
+ * import { authenticate } from "../../shopify.server";
162
+ * import { getWidgets } from "~/db/widgets";
163
+ *
164
+ * export async function loader({ request }: LoaderFunctionArgs) {
165
+ * const {sessionToken} = authenticate.public.checkout(request);
166
+ *
167
+ * return json(await getWidgets(sessionToken));
168
+ * }
169
+ * ```
170
+ */
171
+ public: AuthenticatePublic;
172
+
173
+ /**
174
+ * Authenticate a Shopify webhook request, get back an authenticated admin context and details on the webhook request
175
+ *
176
+ * @example
177
+ * <caption>Authenticating a webhook request</caption>
178
+ *
179
+ * ```ts
180
+ * // app/routes/webhooks.ts
181
+ * import { ActionFunctionArgs } from "@remix-run/node";
182
+ * import { authenticate } from "../shopify.server";
183
+ * import db from "../db.server";
184
+ *
185
+ * export const action = async ({ request }: ActionFunctionArgs) => {
186
+ * const { topic, shop, session, payload } = await authenticate.webhook(request);
187
+ *
188
+ * // Webhook requests can trigger after an app is uninstalled
189
+ * // If the app is already uninstalled, the session may be undefined.
190
+ * if (!session) {
191
+ * throw new Response();
192
+ * }
193
+ *
194
+ * // Handle the webhook
195
+ * console.log(`${TOPIC} webhook received with payload:`, JSON.stringify(payload))
196
+ *
197
+ * throw new Response();
198
+ * };
199
+ * ```
200
+ *
201
+ * @example
202
+ * <caption>Registering app-specific webhooks (Recommended)</caption>
203
+ * ```toml
204
+ * # shopify.app.toml
205
+ * [webhooks]
206
+ * api_version = "2024-07"
207
+
208
+ * [[webhooks.subscriptions]]
209
+ * topics = ["products/create"]
210
+ * uri = "/webhooks/products/create"
211
+ *
212
+ * ```
213
+ *
214
+ * @example
215
+ * <caption>Registering shop-specific webhooks.</caption>
216
+ * <description>In many cases you won't need this. Please see: [https://shopify.dev/docs/apps/build/webhooks/subscribe#app-specific-vs-shop-specific-subscriptions](https://shopify.dev/docs/apps/build/webhooks/subscribe#app-specific-vs-shop-specific-subscriptions)
217
+ * </description>
218
+ * ```ts
219
+ * // app/shopify.server.ts
220
+ * import { DeliveryMethod, shopifyApp } from "@shopify/shopify-app-remix/server";
221
+ *
222
+ * const shopify = shopifyApp({
223
+ * webhooks: {
224
+ * PRODUCTS_CREATE: {
225
+ * deliveryMethod: DeliveryMethod.Http,
226
+ * callbackUrl: "/webhooks/products/create",
227
+ * },
228
+ * },
229
+ * hooks: {
230
+ * afterAuth: async ({ session }) => {
231
+ * // Register webhooks for the shop
232
+ * // In this example, every shop will have these webhooks
233
+ * // You could wrap this in some custom shop specific conditional logic if needed
234
+ * shopify.registerWebhooks({ session });
235
+ * },
236
+ * },
237
+ * // ...etc
238
+ * });
239
+ * ```
240
+ */
241
+ webhook: AuthenticateWebhook<string>;
242
+ }
243
+
244
+ export interface ShopifyAppBase<Config extends AppConfigArg> {
245
+ /**
246
+ * The `SessionStorage` instance you passed in as a config option.
247
+ *
248
+ * @example
249
+ * <caption>Storing sessions with Prisma.</caption>
250
+ * <description>Import the `@shopify/shopify-app-session-storage-prisma` package to store sessions in your Prisma database.</description>
251
+ * ```ts
252
+ * // /app/shopify.server.ts
253
+ * import { shopifyApp } from "@shopify/shopify-app-remix/server";
254
+ * import { PrismaSessionStorage } from "@shopify/shopify-app-session-storage-prisma";
255
+ * import prisma from "~/db.server";
256
+ *
257
+ * const shopify = shopifyApp({
258
+ * sessionStorage: new PrismaSessionStorage(prisma),
259
+ * // ...etc
260
+ * })
261
+ *
262
+ * // shopify.sessionStorage is an instance of PrismaSessionStorage
263
+ * ```
264
+ */
265
+ sessionStorage?: SessionStorageType<Config>;
266
+
267
+ /**
268
+ * Adds the required Content Security Policy headers for Shopify apps to the given Headers object.
269
+ *
270
+ * {@link https://shopify.dev/docs/apps/store/security/iframe-protection}
271
+ *
272
+ * @example
273
+ * <caption>Return headers on all requests.</caption>
274
+ * <description>Add headers to all HTML requests by calling `shopify.addDocumentResponseHeaders` in `entry.server.tsx`.</description>
275
+ *
276
+ * ```
277
+ * // ~/shopify.server.ts
278
+ * import { shopifyApp } from "@shopify/shopify-app-remix/server";
279
+ *
280
+ * const shopify = shopifyApp({
281
+ * // ...etc
282
+ * });
283
+ * export default shopify;
284
+ * export const addDocumentResponseheaders = shopify.addDocumentResponseheaders;
285
+ * ```
286
+ *
287
+ * ```ts
288
+ * // entry.server.tsx
289
+ * import { addDocumentResponseHeaders } from "~/shopify.server";
290
+ *
291
+ * export default function handleRequest(
292
+ * request: Request,
293
+ * responseStatusCode: number,
294
+ * responseHeaders: Headers,
295
+ * remixContext: EntryContext
296
+ * ) {
297
+ * const markup = renderToString(
298
+ * <RemixServer context={remixContext} url={request.url} />
299
+ * );
300
+ *
301
+ * responseHeaders.set("Content-Type", "text/html");
302
+ * addDocumentResponseHeaders(request, responseHeaders);
303
+ *
304
+ * return new Response("<!DOCTYPE html>" + markup, {
305
+ * status: responseStatusCode,
306
+ * headers: responseHeaders,
307
+ * });
308
+ * }
309
+ * ```
310
+ */
311
+ addDocumentResponseHeaders: AddDocumentResponseHeaders;
312
+
313
+ /**
314
+ * Register shop-specific webhook subscriptions using the Admin GraphQL API.
315
+ *
316
+ * In many cases defining app-specific webhooks in the `shopify.app.toml` will be sufficient and easier to manage. Please see:
317
+ *
318
+ * {@link https://shopify.dev/docs/apps/build/webhooks/subscribe#app-specific-vs-shop-specific-subscriptions}
319
+ *
320
+ * You should only use this if you need shop-specific webhooks.
321
+ *
322
+ * @example
323
+ * <caption>Registering shop-specific webhooks after install</caption>
324
+ * <description>Trigger the registration to create the shop-specific webhook subscriptions after a merchant installs your app using the `afterAuth` hook. Learn more about [subscribing to webhooks.](https://shopify.dev/docs/api/shopify-app-remix/v3/guide-webhooks)</description>
325
+ * ```ts
326
+ * // app/shopify.server.ts
327
+ * import { DeliveryMethod, shopifyApp } from "@shopify/shopify-app-remix/server";
328
+ *
329
+ * const shopify = shopifyApp({
330
+ * webhooks: {
331
+ * PRODUCTS_CREATE: {
332
+ * deliveryMethod: DeliveryMethod.Http,
333
+ * callbackUrl: "/webhooks/products/create",
334
+ * },
335
+ * },
336
+ * hooks: {
337
+ * afterAuth: async ({ session }) => {
338
+ * // Register webhooks for the shop
339
+ * // In this example, every shop will have these webhooks
340
+ * // You could wrap this in some custom shop specific conditional logic if needed
341
+ * shopify.registerWebhooks({ session });
342
+ * },
343
+ * },
344
+ * // ...etc
345
+ * });
346
+ * ```
347
+ */
348
+ registerWebhooks: RegisterWebhooks;
349
+
350
+ /**
351
+ * Ways to authenticate requests from different surfaces across Shopify.
352
+ *
353
+ * @example
354
+ * <caption>Authenticate Shopify requests.</caption>
355
+ * <description>Use the functions in `authenticate` to validate requests coming from Shopify.</description>
356
+ * ```ts
357
+ * // /app/shopify.server.ts
358
+ * import { ApiVersion, shopifyApp } from "@shopify/shopify-app-remix/server";
359
+ *
360
+ * const shopify = shopifyApp({
361
+ * // ...etc
362
+ * });
363
+ * export default shopify;
364
+ * ```
365
+ * ```ts
366
+ * // /app/routes/**\/*.jsx
367
+ * import { LoaderFunctionArgs, json } from "@remix-run/node";
368
+ * import shopify from "../../shopify.server";
369
+ *
370
+ * export async function loader({ request }: LoaderFunctionArgs) {
371
+ * const {admin, session, sessionToken, billing} = shopify.authenticate.admin(request);
372
+ * const response = admin.graphql(`{ shop { name } }`)
373
+ *
374
+ * return json(await response.json());
375
+ * }
376
+ * ```
377
+ */
378
+ authenticate: Authenticate<Config>;
379
+
380
+ /**
381
+ * Ways to get Contexts from requests that do not originate from Shopify.
382
+ *
383
+ * @example
384
+ * <caption>Using unauthenticated contexts.</caption>
385
+ * <description>Create contexts for requests that don't come from Shopify.</description>
386
+ * ```ts
387
+ * // /app/shopify.server.ts
388
+ * import { ApiVersion, shopifyApp } from "@shopify/shopify-app-remix/server";
389
+ *
390
+ * const shopify = shopifyApp({
391
+ * // ...etc
392
+ * });
393
+ * export default shopify;
394
+ * ```
395
+ * ```ts
396
+ * // /app/routes/**\/*.jsx
397
+ * import { LoaderFunctionArgs, json } from "@remix-run/node";
398
+ * import { authenticateExternal } from "~/helpers/authenticate"
399
+ * import shopify from "../../shopify.server";
400
+ *
401
+ * export async function loader({ request }: LoaderFunctionArgs) {
402
+ * const shop = await authenticateExternal(request)
403
+ * const {admin} = await shopify.unauthenticated.admin(shop);
404
+ * const response = admin.graphql(`{ shop { currencyCode } }`)
405
+ *
406
+ * return json(await response.json());
407
+ * }
408
+ * ```
409
+ */
410
+ unauthenticated: Unauthenticated;
411
+ }
412
+
413
+ export interface ShopifyAppLogin {
414
+ /**
415
+ * Log a merchant in, and redirect them to the app root. Will redirect the merchant to authentication if a shop is
416
+ * present in the URL search parameters or form data.
417
+ *
418
+ * This function won't be present when the `distribution` config option is set to `AppDistribution.ShopifyAdmin`,
419
+ * because Admin apps aren't allowed to show a login page.
420
+ *
421
+ * @example
422
+ * <caption>Creating a login page.</caption>
423
+ * <description>Use `shopify.login` to create a login form, in a route that can handle GET and POST requests.</description>
424
+ * ```ts
425
+ * // /app/shopify.server.ts
426
+ * import { ApiVersion, shopifyApp } from "@shopify/shopify-app-remix/server";
427
+ *
428
+ * const shopify = shopifyApp({
429
+ * // ...etc
430
+ * });
431
+ * export default shopify;
432
+ * ```
433
+ * ```ts
434
+ * // /app/routes/auth/login.tsx
435
+ * import shopify from "../../shopify.server";
436
+ *
437
+ * export async function loader({ request }: LoaderFunctionArgs) {
438
+ * const errors = shopify.login(request);
439
+ *
440
+ * return json(errors);
441
+ * }
442
+ *
443
+ * export async function action({ request }: ActionFunctionArgs) {
444
+ * const errors = shopify.login(request);
445
+ *
446
+ * return json(errors);
447
+ * }
448
+ *
449
+ * export default function Auth() {
450
+ * const actionData = useActionData<typeof action>();
451
+ * const [shop, setShop] = useState("");
452
+ *
453
+ * return (
454
+ * <Page>
455
+ * <Card>
456
+ * <Form method="post">
457
+ * <FormLayout>
458
+ * <Text variant="headingMd" as="h2">
459
+ * Login
460
+ * </Text>
461
+ * <TextField
462
+ * type="text"
463
+ * name="shop"
464
+ * label="Shop domain"
465
+ * helpText="e.g: my-shop-domain.myshopify.com"
466
+ * value={shop}
467
+ * onChange={setShop}
468
+ * autoComplete="on"
469
+ * error={actionData?.errors.shop}
470
+ * />
471
+ * <Button submit primary>
472
+ * Submit
473
+ * </Button>
474
+ * </FormLayout>
475
+ * </Form>
476
+ * </Card>
477
+ * </Page>
478
+ * );
479
+ * }
480
+ * ```
481
+ */
482
+ login: Login;
483
+ }
484
+
485
+ export type AdminApp<Config extends AppConfigArg> = ShopifyAppBase<Config>;
486
+ export type SingleMerchantApp<Config extends AppConfigArg> =
487
+ ShopifyAppBase<Config> & ShopifyAppLogin;
488
+ export type AppStoreApp<Config extends AppConfigArg> = ShopifyAppBase<Config> &
489
+ ShopifyAppLogin;
490
+
491
+ type EnforceSessionStorage<Config extends AppConfigArg, Base> = Base & {
492
+ sessionStorage: SessionStorageType<Config>;
493
+ };
494
+
495
+ /**
496
+ * An object your app can use to interact with Shopify.
497
+ *
498
+ * By default, the app's distribution is `AppStore`.
499
+ */
500
+ export type ShopifyApp<Config extends AppConfigArg> =
501
+ Config['distribution'] extends AppDistribution.ShopifyAdmin
502
+ ? AdminApp<Config>
503
+ : Config['distribution'] extends AppDistribution.SingleMerchant
504
+ ? EnforceSessionStorage<Config, SingleMerchantApp<Config>>
505
+ : Config['distribution'] extends AppDistribution.AppStore
506
+ ? EnforceSessionStorage<Config, AppStoreApp<Config>>
507
+ : EnforceSessionStorage<Config, AppStoreApp<Config>>;
@@ -0,0 +1,23 @@
1
+ import {ensureValidOfflineSession} from '../../helpers';
2
+ import {SessionNotFoundError} from '../../errors';
3
+ import {BasicParams} from '../../types';
4
+ import {adminClientFactory} from '../../clients/admin';
5
+
6
+ import {UnauthenticatedAdminContext} from './types';
7
+
8
+ export function unauthenticatedAdminContextFactory(params: BasicParams) {
9
+ return async (shop: string): Promise<UnauthenticatedAdminContext> => {
10
+ const session = await ensureValidOfflineSession(params, shop);
11
+
12
+ if (!session) {
13
+ throw new SessionNotFoundError(
14
+ `Could not find a session for shop ${shop} when creating unauthenticated admin context`,
15
+ );
16
+ }
17
+
18
+ return {
19
+ session,
20
+ admin: adminClientFactory({params, session}),
21
+ };
22
+ };
23
+ }
@@ -0,0 +1 @@
1
+ export * from './factory';
@@ -0,0 +1,79 @@
1
+ import {Session} from '@shopify/shopify-api';
2
+
3
+ import {AdminApiContext} from '../../clients';
4
+
5
+ export interface UnauthenticatedAdminContext {
6
+ /**
7
+ * The session for the given shop.
8
+ *
9
+ * This comes from the session storage which `shopifyApp` uses to store sessions in your database of choice.
10
+ *
11
+ * This will always be an offline session. You can use to get shop-specific data.
12
+ *
13
+ * @example
14
+ * <caption>Using the offline session.</caption>
15
+ * <description>Get your app's shop-specific data using the returned offline `session` object.</description>
16
+ * ```ts
17
+ * // /app/routes/**\/*.ts
18
+ * import { LoaderFunctionArgs, json } from "@remix-run/node";
19
+ * import { unauthenticated } from "../shopify.server";
20
+ * import { getMyAppData } from "~/db/model.server";
21
+ *
22
+ * export const loader = async ({ request }: LoaderFunctionArgs) => {
23
+ * const shop = getShopFromExternalRequest(request);
24
+ * const { session } = await unauthenticated.admin(shop);
25
+ * return json(await getMyAppData({shop: session.shop));
26
+ * };
27
+ * ```
28
+ */
29
+ session: Session;
30
+
31
+ /**
32
+ * Methods for interacting with the GraphQL Admin API for the given store.
33
+ *
34
+ * @example
35
+ * <caption>Querying the GraphQL API.</caption>
36
+ * <description>Use `admin.graphql` to make query / mutation requests.</description>
37
+ * ```ts
38
+ * // /app/routes/**\/*.ts
39
+ * import { ActionFunctionArgs } from "@remix-run/node";
40
+ * import { unauthenticated } from "../shopify.server";
41
+ *
42
+ * export async function action({ request }: ActionFunctionArgs) {
43
+ * const shop = getShopFromExternalRequest(request);
44
+ * const { admin } = await unauthenticated.admin(shop);
45
+ *
46
+ * const response = await admin.graphql(
47
+ * `#graphql
48
+ * mutation populateProduct($input: ProductInput!) {
49
+ * productCreate(input: $input) {
50
+ * product {
51
+ * id
52
+ * }
53
+ * }
54
+ * }`,
55
+ * { variables: { input: { title: "Product Name" } } }
56
+ * );
57
+ *
58
+ * const productData = await response.json();
59
+ * return json({ data: productData.data });
60
+ * }
61
+ * ```
62
+ *
63
+ * ```ts
64
+ * // /app/shopify.server.ts
65
+ * import { shopifyApp } from "@shopify/shopify-app-remix/server";
66
+ *
67
+ * const shopify = shopifyApp({
68
+ * // ...etc
69
+ * });
70
+ * export default shopify;
71
+ * export const unauthenticated = shopify.unauthenticated;
72
+ * ```
73
+ */
74
+ admin: AdminApiContext;
75
+ }
76
+
77
+ export type GetUnauthenticatedAdminContext = (
78
+ shop: string,
79
+ ) => Promise<UnauthenticatedAdminContext>;
@@ -0,0 +1,40 @@
1
+ import {ReferenceEntityTemplateSchema} from '@shopify/generate-docs';
2
+
3
+ const data: ReferenceEntityTemplateSchema = {
4
+ name: 'Unauthenticated admin',
5
+ description: `Allows interacting with the Admin API when working outside of Shopify requests.
6
+ This enables apps to integrate with 3rd party services and perform background tasks.
7
+
8
+ > Caution:
9
+ > This function doesn't perform **any** validation and shouldn't rely on raw user input.
10
+
11
+ When using this function, consider the following:
12
+
13
+ #### Background tasks
14
+
15
+ Apps should ensure that the shop domain is authenticated when enqueueing jobs.
16
+
17
+ #### 3rd party service requests
18
+
19
+ Apps must obtain the shop domain from the 3rd party service in a secure way.`,
20
+ category: 'Unauthenticated',
21
+ type: 'object',
22
+ isVisualComponent: false,
23
+ definitions: [
24
+ {
25
+ title: 'unauthenticated.admin',
26
+ description: 'Creates an unauthenticated Admin context.',
27
+ type: 'GetUnauthenticatedAdminContext',
28
+ },
29
+ ],
30
+ jsDocTypeExamples: ['UnauthenticatedAdminContext'],
31
+ related: [
32
+ {
33
+ name: 'API context',
34
+ subtitle: 'Interact with the Admin API.',
35
+ url: '/docs/api/shopify-app-remix/apis/admin-api',
36
+ },
37
+ ],
38
+ };
39
+
40
+ export default data;
@@ -0,0 +1,28 @@
1
+ import {SessionNotFoundError} from '../../errors';
2
+ import {BasicParams} from '../../types';
3
+ import {storefrontClientFactory} from '../../clients/storefront';
4
+ import {ensureValidOfflineSession} from '../../helpers';
5
+
6
+ import {
7
+ UnauthenticatedStorefrontContext,
8
+ GetUnauthenticatedStorefrontContext,
9
+ } from './types';
10
+
11
+ export function unauthenticatedStorefrontContextFactory(
12
+ params: BasicParams,
13
+ ): GetUnauthenticatedStorefrontContext {
14
+ return async (shop: string): Promise<UnauthenticatedStorefrontContext> => {
15
+ const session = await ensureValidOfflineSession(params, shop);
16
+
17
+ if (!session) {
18
+ throw new SessionNotFoundError(
19
+ `Could not find a session for shop ${shop} when creating unauthenticated storefront context`,
20
+ );
21
+ }
22
+
23
+ return {
24
+ session,
25
+ storefront: storefrontClientFactory({params, session}),
26
+ };
27
+ };
28
+ }
@@ -0,0 +1 @@
1
+ export * from './factory';