@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.
Files changed (276) hide show
  1. package/dist/cjs/react/components/AppProvider/AppProvider.js.map +1 -1
  2. package/dist/cjs/react/components/AppProxyForm/AppProxyForm.js +4 -3
  3. package/dist/cjs/react/components/AppProxyForm/AppProxyForm.js.map +1 -1
  4. package/dist/cjs/react/components/AppProxyLink/AppProxyLink.js +4 -3
  5. package/dist/cjs/react/components/AppProxyLink/AppProxyLink.js.map +1 -1
  6. package/dist/cjs/react/components/AppProxyProvider/AppProxyProvider.js.map +1 -1
  7. package/dist/cjs/server/authenticate/admin/helpers/redirect.js +2 -2
  8. package/dist/cjs/server/authenticate/admin/helpers/redirect.js.map +1 -1
  9. package/dist/cjs/server/authenticate/admin/strategies/auth-code-flow.js.map +1 -1
  10. package/dist/cjs/server/authenticate/admin/strategies/merchant-custom-app.js.map +1 -1
  11. package/dist/cjs/server/authenticate/admin/strategies/token-exchange.js.map +1 -1
  12. package/dist/cjs/server/authenticate/webhooks/authenticate.js +34 -10
  13. package/dist/cjs/server/authenticate/webhooks/authenticate.js.map +1 -1
  14. package/dist/cjs/server/future/flags.js.map +1 -1
  15. package/dist/cjs/server/shopify-app.js +1 -0
  16. package/dist/cjs/server/shopify-app.js.map +1 -1
  17. package/dist/cjs/server/version.js +1 -1
  18. package/dist/cjs/server/version.js.map +1 -1
  19. package/dist/esm/react/components/AppProvider/AppProvider.mjs.map +1 -1
  20. package/dist/esm/react/components/AppProxyForm/AppProxyForm.mjs +5 -4
  21. package/dist/esm/react/components/AppProxyForm/AppProxyForm.mjs.map +1 -1
  22. package/dist/esm/react/components/AppProxyLink/AppProxyLink.mjs +5 -4
  23. package/dist/esm/react/components/AppProxyLink/AppProxyLink.mjs.map +1 -1
  24. package/dist/esm/react/components/AppProxyProvider/AppProxyProvider.mjs.map +1 -1
  25. package/dist/esm/server/authenticate/admin/helpers/redirect.mjs +2 -2
  26. package/dist/esm/server/authenticate/admin/helpers/redirect.mjs.map +1 -1
  27. package/dist/esm/server/authenticate/admin/strategies/auth-code-flow.mjs.map +1 -1
  28. package/dist/esm/server/authenticate/admin/strategies/merchant-custom-app.mjs.map +1 -1
  29. package/dist/esm/server/authenticate/admin/strategies/token-exchange.mjs.map +1 -1
  30. package/dist/esm/server/authenticate/webhooks/authenticate.mjs +35 -11
  31. package/dist/esm/server/authenticate/webhooks/authenticate.mjs.map +1 -1
  32. package/dist/esm/server/future/flags.mjs.map +1 -1
  33. package/dist/esm/server/shopify-app.mjs +1 -0
  34. package/dist/esm/server/shopify-app.mjs.map +1 -1
  35. package/dist/esm/server/version.mjs +1 -1
  36. package/dist/esm/server/version.mjs.map +1 -1
  37. package/dist/ts/react/components/AppProvider/AppProvider.d.ts +5 -1
  38. package/dist/ts/react/components/AppProvider/AppProvider.d.ts.map +1 -1
  39. package/dist/ts/react/components/AppProxyForm/AppProxyForm.d.ts +2 -1
  40. package/dist/ts/react/components/AppProxyForm/AppProxyForm.d.ts.map +1 -1
  41. package/dist/ts/react/components/AppProxyLink/AppProxyLink.d.ts +2 -1
  42. package/dist/ts/react/components/AppProxyLink/AppProxyLink.d.ts.map +1 -1
  43. package/dist/ts/react/components/AppProxyProvider/AppProxyProvider.d.ts +5 -1
  44. package/dist/ts/react/components/AppProxyProvider/AppProxyProvider.d.ts.map +1 -1
  45. package/dist/ts/server/authenticate/admin/billing/types.d.ts +4 -0
  46. package/dist/ts/server/authenticate/admin/billing/types.d.ts.map +1 -1
  47. package/dist/ts/server/authenticate/admin/scope/types.d.ts +2 -1
  48. package/dist/ts/server/authenticate/admin/scope/types.d.ts.map +1 -1
  49. package/dist/ts/server/authenticate/admin/strategies/auth-code-flow.d.ts.map +1 -1
  50. package/dist/ts/server/authenticate/admin/strategies/merchant-custom-app.d.ts.map +1 -1
  51. package/dist/ts/server/authenticate/admin/strategies/token-exchange.d.ts.map +1 -1
  52. package/dist/ts/server/authenticate/admin/types.d.ts +10 -0
  53. package/dist/ts/server/authenticate/admin/types.d.ts.map +1 -1
  54. package/dist/ts/server/authenticate/flow/types.d.ts +4 -0
  55. package/dist/ts/server/authenticate/flow/types.d.ts.map +1 -1
  56. package/dist/ts/server/authenticate/fulfillment-service/types.d.ts +4 -0
  57. package/dist/ts/server/authenticate/fulfillment-service/types.d.ts.map +1 -1
  58. package/dist/ts/server/authenticate/public/appProxy/types.d.ts +4 -0
  59. package/dist/ts/server/authenticate/public/appProxy/types.d.ts.map +1 -1
  60. package/dist/ts/server/authenticate/public/checkout/types.d.ts +4 -0
  61. package/dist/ts/server/authenticate/public/checkout/types.d.ts.map +1 -1
  62. package/dist/ts/server/authenticate/public/customer-account/types.d.ts +4 -0
  63. package/dist/ts/server/authenticate/public/customer-account/types.d.ts.map +1 -1
  64. package/dist/ts/server/authenticate/public/extension/types.d.ts.map +1 -1
  65. package/dist/ts/server/authenticate/webhooks/authenticate.d.ts.map +1 -1
  66. package/dist/ts/server/authenticate/webhooks/types.d.ts +36 -3
  67. package/dist/ts/server/authenticate/webhooks/types.d.ts.map +1 -1
  68. package/dist/ts/server/boundary/error.d.ts +1 -1
  69. package/dist/ts/server/boundary/error.d.ts.map +1 -1
  70. package/dist/ts/server/clients/admin/types.d.ts +4 -0
  71. package/dist/ts/server/clients/admin/types.d.ts.map +1 -1
  72. package/dist/ts/server/clients/storefront/types.d.ts +4 -0
  73. package/dist/ts/server/clients/storefront/types.d.ts.map +1 -1
  74. package/dist/ts/server/config-types.d.ts.map +1 -1
  75. package/dist/ts/server/future/flags.d.ts +6 -0
  76. package/dist/ts/server/future/flags.d.ts.map +1 -1
  77. package/dist/ts/server/shopify-app.d.ts +1 -0
  78. package/dist/ts/server/shopify-app.d.ts.map +1 -1
  79. package/dist/ts/server/unauthenticated/admin/types.d.ts +4 -0
  80. package/dist/ts/server/unauthenticated/admin/types.d.ts.map +1 -1
  81. package/dist/ts/server/unauthenticated/storefront/types.d.ts +4 -0
  82. package/dist/ts/server/unauthenticated/storefront/types.d.ts.map +1 -1
  83. package/dist/ts/server/version.d.ts +1 -1
  84. package/dist/tsconfig.build.tsbuildinfo +1 -1
  85. package/package.json +19 -15
  86. package/src/react/components/AppProvider/AppProvider.tsx +151 -0
  87. package/src/react/components/AppProvider/index.ts +1 -0
  88. package/src/react/components/AppProxyForm/AppProxyForm.tsx +85 -0
  89. package/src/react/components/AppProxyForm/index.ts +1 -0
  90. package/src/react/components/AppProxyLink/AppProxyLink.tsx +61 -0
  91. package/src/react/components/AppProxyLink/index.ts +1 -0
  92. package/src/react/components/AppProxyProvider/AppProxyProvider.tsx +104 -0
  93. package/src/react/components/AppProxyProvider/index.ts +5 -0
  94. package/src/react/components/RemixPolarisLink.tsx +14 -0
  95. package/src/react/components/index.ts +4 -0
  96. package/src/react/const.ts +2 -0
  97. package/src/react/index.ts +1 -0
  98. package/src/server/__test-helpers/const.ts +14 -0
  99. package/src/server/__test-helpers/expect-admin-api-client.ts +51 -0
  100. package/src/server/__test-helpers/expect-begin-auth-redirect.ts +22 -0
  101. package/src/server/__test-helpers/expect-document-request-headers.ts +25 -0
  102. package/src/server/__test-helpers/expect-exit-iframe.ts +37 -0
  103. package/src/server/__test-helpers/expect-login-redirect.ts +8 -0
  104. package/src/server/__test-helpers/expect-storefront-api-client.ts +49 -0
  105. package/src/server/__test-helpers/expect-token-refresh.ts +188 -0
  106. package/src/server/__test-helpers/get-hmac.ts +7 -0
  107. package/src/server/__test-helpers/get-jwt.ts +10 -0
  108. package/src/server/__test-helpers/get-thrown-error.ts +19 -0
  109. package/src/server/__test-helpers/get-thrown-response.ts +17 -0
  110. package/src/server/__test-helpers/index.ts +21 -0
  111. package/src/server/__test-helpers/mock-graphql-request.ts +60 -0
  112. package/src/server/__test-helpers/request-mock.ts +169 -0
  113. package/src/server/__test-helpers/setup-embedded-flow.ts +30 -0
  114. package/src/server/__test-helpers/setup-fetch-flow.ts +29 -0
  115. package/src/server/__test-helpers/setup-non-embedded-flow.ts +31 -0
  116. package/src/server/__test-helpers/setup-valid-request.ts +78 -0
  117. package/src/server/__test-helpers/setup-valid-session.ts +28 -0
  118. package/src/server/__test-helpers/sign-request-cookie.ts +32 -0
  119. package/src/server/__test-helpers/test-config.ts +56 -0
  120. package/src/server/adapters/node/index.ts +13 -0
  121. package/src/server/adapters/vercel/index.ts +13 -0
  122. package/src/server/authenticate/admin/authenticate.admin.doc.example.ts +34 -0
  123. package/src/server/authenticate/admin/authenticate.ts +232 -0
  124. package/src/server/authenticate/admin/billing/cancel.ts +38 -0
  125. package/src/server/authenticate/admin/billing/check.ts +41 -0
  126. package/src/server/authenticate/admin/billing/create-usage-record.ts +36 -0
  127. package/src/server/authenticate/admin/billing/helpers.ts +42 -0
  128. package/src/server/authenticate/admin/billing/index.ts +6 -0
  129. package/src/server/authenticate/admin/billing/request.ts +64 -0
  130. package/src/server/authenticate/admin/billing/require.ts +58 -0
  131. package/src/server/authenticate/admin/billing/types.ts +617 -0
  132. package/src/server/authenticate/admin/billing/update-usage-subscription-capped-amount.ts +55 -0
  133. package/src/server/authenticate/admin/helpers/begin-auth.ts +17 -0
  134. package/src/server/authenticate/admin/helpers/create-admin-api-context.ts +20 -0
  135. package/src/server/authenticate/admin/helpers/ensure-app-is-embedded-if-required.ts +18 -0
  136. package/src/server/authenticate/admin/helpers/ensure-session-token-search-param-if-required.ts +25 -0
  137. package/src/server/authenticate/admin/helpers/handle-client-error.ts +45 -0
  138. package/src/server/authenticate/admin/helpers/index.ts +14 -0
  139. package/src/server/authenticate/admin/helpers/redirect-to-auth-page.ts +28 -0
  140. package/src/server/authenticate/admin/helpers/redirect-to-bounce-page.ts +23 -0
  141. package/src/server/authenticate/admin/helpers/redirect-to-install-page.ts +53 -0
  142. package/src/server/authenticate/admin/helpers/redirect-to-shopify-or-app-root.ts +21 -0
  143. package/src/server/authenticate/admin/helpers/redirect-with-app-bridge-headers.ts +13 -0
  144. package/src/server/authenticate/admin/helpers/redirect-with-exitiframe.ts +28 -0
  145. package/src/server/authenticate/admin/helpers/redirect.ts +164 -0
  146. package/src/server/authenticate/admin/helpers/render-app-bridge.ts +46 -0
  147. package/src/server/authenticate/admin/helpers/trigger-after-auth-hook.ts +29 -0
  148. package/src/server/authenticate/admin/helpers/validate-redirect-url.ts +67 -0
  149. package/src/server/authenticate/admin/helpers/validate-shop-and-host-params.ts +46 -0
  150. package/src/server/authenticate/admin/scope/client/fetch-scopes-details.ts +43 -0
  151. package/src/server/authenticate/admin/scope/client/revoke-scopes.ts +38 -0
  152. package/src/server/authenticate/admin/scope/factory.ts +21 -0
  153. package/src/server/authenticate/admin/scope/query.ts +51 -0
  154. package/src/server/authenticate/admin/scope/request.ts +32 -0
  155. package/src/server/authenticate/admin/scope/revoke.ts +48 -0
  156. package/src/server/authenticate/admin/scope/types.ts +127 -0
  157. package/src/server/authenticate/admin/strategies/auth-code-flow.ts +332 -0
  158. package/src/server/authenticate/admin/strategies/merchant-custom-app.ts +65 -0
  159. package/src/server/authenticate/admin/strategies/token-exchange.ts +188 -0
  160. package/src/server/authenticate/admin/strategies/types.ts +29 -0
  161. package/src/server/authenticate/admin/types.ts +226 -0
  162. package/src/server/authenticate/const.ts +15 -0
  163. package/src/server/authenticate/flow/authenticate.flow.doc.example.ts +39 -0
  164. package/src/server/authenticate/flow/authenticate.ts +68 -0
  165. package/src/server/authenticate/flow/types.ts +89 -0
  166. package/src/server/authenticate/fulfillment-service/authenticate.fulfillment-service.doc.example.ts +52 -0
  167. package/src/server/authenticate/fulfillment-service/authenticate.ts +82 -0
  168. package/src/server/authenticate/fulfillment-service/types.ts +86 -0
  169. package/src/server/authenticate/helpers/add-response-headers.ts +43 -0
  170. package/src/server/authenticate/helpers/app-bridge-url.ts +10 -0
  171. package/src/server/authenticate/helpers/ensure-cors-headers.ts +38 -0
  172. package/src/server/authenticate/helpers/get-session-token-header.ts +11 -0
  173. package/src/server/authenticate/helpers/get-shop-from-request.ts +4 -0
  174. package/src/server/authenticate/helpers/idempotent-promise-handler.ts +45 -0
  175. package/src/server/authenticate/helpers/index.ts +10 -0
  176. package/src/server/authenticate/helpers/invalidate-access-token.ts +17 -0
  177. package/src/server/authenticate/helpers/reject-bot-request.ts +27 -0
  178. package/src/server/authenticate/helpers/respond-to-invalid-session-token.ts +28 -0
  179. package/src/server/authenticate/helpers/respond-to-options-request.ts +26 -0
  180. package/src/server/authenticate/helpers/validate-session-token.ts +40 -0
  181. package/src/server/authenticate/login/login.ts +53 -0
  182. package/src/server/authenticate/public/appProxy/authenticate.public.app-proxy.doc.example.ts +27 -0
  183. package/src/server/authenticate/public/appProxy/authenticate.ts +150 -0
  184. package/src/server/authenticate/public/appProxy/types.ts +219 -0
  185. package/src/server/authenticate/public/checkout/authenticate.public.checkout.doc.example.offers.ts +21 -0
  186. package/src/server/authenticate/public/checkout/authenticate.public.checkout.doc.example.ts +17 -0
  187. package/src/server/authenticate/public/checkout/authenticate.ts +13 -0
  188. package/src/server/authenticate/public/checkout/types.ts +69 -0
  189. package/src/server/authenticate/public/customer-account/authenticate.public.customerAccount.doc.example.offers.ts +36 -0
  190. package/src/server/authenticate/public/customer-account/authenticate.public.customerAccount.doc.example.ts +18 -0
  191. package/src/server/authenticate/public/customer-account/authenticate.ts +13 -0
  192. package/src/server/authenticate/public/customer-account/types.ts +69 -0
  193. package/src/server/authenticate/public/extension/authenticate.ts +54 -0
  194. package/src/server/authenticate/public/extension/types.ts +11 -0
  195. package/src/server/authenticate/public/factory.ts +24 -0
  196. package/src/server/authenticate/public/index.ts +1 -0
  197. package/src/server/authenticate/public/pos/authenticate.ts +8 -0
  198. package/src/server/authenticate/public/pos/types.ts +65 -0
  199. package/src/server/authenticate/public/types.ts +96 -0
  200. package/src/server/authenticate/webhooks/authenticate.ts +105 -0
  201. package/src/server/authenticate/webhooks/authenticate.webhooks.doc.example.ts +43 -0
  202. package/src/server/authenticate/webhooks/index.ts +1 -0
  203. package/src/server/authenticate/webhooks/register.ts +48 -0
  204. package/src/server/authenticate/webhooks/types.ts +267 -0
  205. package/src/server/boundary/error.tsx +14 -0
  206. package/src/server/boundary/headers.ts +15 -0
  207. package/src/server/boundary/index.ts +39 -0
  208. package/src/server/boundary/types.ts +4 -0
  209. package/src/server/clients/admin/factory.ts +22 -0
  210. package/src/server/clients/admin/graphql.ts +39 -0
  211. package/src/server/clients/admin/index.ts +2 -0
  212. package/src/server/clients/admin/types.ts +124 -0
  213. package/src/server/clients/index.ts +2 -0
  214. package/src/server/clients/storefront/factory.ts +32 -0
  215. package/src/server/clients/storefront/index.ts +2 -0
  216. package/src/server/clients/storefront/types.ts +82 -0
  217. package/src/server/clients/types.ts +47 -0
  218. package/src/server/config-types.ts +309 -0
  219. package/src/server/errors.ts +3 -0
  220. package/src/server/future/flags.ts +68 -0
  221. package/src/server/helpers/create-or-load-offline-session.ts +19 -0
  222. package/src/server/helpers/ensure-offline-token-is-not-expired.ts +32 -0
  223. package/src/server/helpers/ensure-valid-offline-session.ts +15 -0
  224. package/src/server/helpers/index.ts +4 -0
  225. package/src/server/helpers/refresh-token.ts +35 -0
  226. package/src/server/index.ts +24 -0
  227. package/src/server/override-logger.ts +42 -0
  228. package/src/server/shopify-app.ts +216 -0
  229. package/src/server/test-helpers/index.ts +1 -0
  230. package/src/server/test-helpers/test-config.ts +79 -0
  231. package/src/server/types-contexts.ts +44 -0
  232. package/src/server/types.ts +507 -0
  233. package/src/server/unauthenticated/admin/factory.ts +23 -0
  234. package/src/server/unauthenticated/admin/index.ts +1 -0
  235. package/src/server/unauthenticated/admin/types.ts +83 -0
  236. package/src/server/unauthenticated/storefront/factory.ts +28 -0
  237. package/src/server/unauthenticated/storefront/index.ts +1 -0
  238. package/src/server/unauthenticated/storefront/types.ts +109 -0
  239. package/src/server/unauthenticated/types.ts +65 -0
  240. package/src/server/version.ts +1 -0
  241. package/dist/ts/react/components/AppProvider/AppProvider.doc.d.ts +0 -4
  242. package/dist/ts/react/components/AppProvider/AppProvider.doc.d.ts.map +0 -1
  243. package/dist/ts/react/components/AppProxyForm/AppProxyForm.doc.d.ts +0 -4
  244. package/dist/ts/react/components/AppProxyForm/AppProxyForm.doc.d.ts.map +0 -1
  245. package/dist/ts/react/components/AppProxyLink/AppProxyLink.doc.d.ts +0 -4
  246. package/dist/ts/react/components/AppProxyLink/AppProxyLink.doc.d.ts.map +0 -1
  247. package/dist/ts/react/components/AppProxyProvider/AppProxyProvider.doc.d.ts +0 -4
  248. package/dist/ts/react/components/AppProxyProvider/AppProxyProvider.doc.d.ts.map +0 -1
  249. package/dist/ts/server/authenticate/admin/authenticate.admin.doc.d.ts +0 -4
  250. package/dist/ts/server/authenticate/admin/authenticate.admin.doc.d.ts.map +0 -1
  251. package/dist/ts/server/authenticate/admin/billing/authenticate.admin.billing.doc.d.ts +0 -4
  252. package/dist/ts/server/authenticate/admin/billing/authenticate.admin.billing.doc.d.ts.map +0 -1
  253. package/dist/ts/server/authenticate/admin/scope/authenticate.admin.scopes.doc.d.ts +0 -4
  254. package/dist/ts/server/authenticate/admin/scope/authenticate.admin.scopes.doc.d.ts.map +0 -1
  255. package/dist/ts/server/authenticate/flow/authenticate.flow.doc.d.ts +0 -4
  256. package/dist/ts/server/authenticate/flow/authenticate.flow.doc.d.ts.map +0 -1
  257. package/dist/ts/server/authenticate/fulfillment-service/authenticate.fulfillment-service.doc.d.ts +0 -4
  258. package/dist/ts/server/authenticate/fulfillment-service/authenticate.fulfillment-service.doc.d.ts.map +0 -1
  259. package/dist/ts/server/authenticate/public/appProxy/authenticate.public.app-proxy.doc.d.ts +0 -4
  260. package/dist/ts/server/authenticate/public/appProxy/authenticate.public.app-proxy.doc.d.ts.map +0 -1
  261. package/dist/ts/server/authenticate/public/checkout/authenticate.public.checkout.doc.d.ts +0 -4
  262. package/dist/ts/server/authenticate/public/checkout/authenticate.public.checkout.doc.d.ts.map +0 -1
  263. package/dist/ts/server/authenticate/public/customer-account/authenticate.public.customerAccount.doc.d.ts +0 -4
  264. package/dist/ts/server/authenticate/public/customer-account/authenticate.public.customerAccount.doc.d.ts.map +0 -1
  265. package/dist/ts/server/authenticate/webhooks/authenticate.webhooks.doc.d.ts +0 -4
  266. package/dist/ts/server/authenticate/webhooks/authenticate.webhooks.doc.d.ts.map +0 -1
  267. package/dist/ts/server/clients/admin/authenticate.admin.api.doc.d.ts +0 -4
  268. package/dist/ts/server/clients/admin/authenticate.admin.api.doc.d.ts.map +0 -1
  269. package/dist/ts/server/clients/storefront/authenticate.storefront.api.doc.d.ts +0 -4
  270. package/dist/ts/server/clients/storefront/authenticate.storefront.api.doc.d.ts.map +0 -1
  271. package/dist/ts/server/shopify-app.doc.d.ts +0 -4
  272. package/dist/ts/server/shopify-app.doc.d.ts.map +0 -1
  273. package/dist/ts/server/unauthenticated/admin/unauthenticated.admin.doc.d.ts +0 -4
  274. package/dist/ts/server/unauthenticated/admin/unauthenticated.admin.doc.d.ts.map +0 -1
  275. package/dist/ts/server/unauthenticated/storefront/unauthenticated.storefront.doc.d.ts +0 -4
  276. 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';