@shopify/shopify-app-remix 1.0.0-rc.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.
- package/LICENSE.md +9 -0
- package/README.md +373 -0
- package/build/cjs/adapters/node/index.js +13 -0
- package/build/cjs/auth/admin/authenticate.js +599 -0
- package/build/cjs/auth/admin/graphql-client.js +57 -0
- package/build/cjs/auth/admin/rest-client.js +124 -0
- package/build/cjs/auth/const.js +17 -0
- package/build/cjs/auth/helpers/add-response-headers.js +69 -0
- package/build/cjs/auth/helpers/begin-auth.js +18 -0
- package/build/cjs/auth/helpers/get-session-token-header.js +10 -0
- package/build/cjs/auth/helpers/handle-client-error.js +37 -0
- package/build/cjs/auth/helpers/redirect-to-auth-page.js +22 -0
- package/build/cjs/auth/helpers/redirect-with-app-bridge-headers.js +31 -0
- package/build/cjs/auth/helpers/redirect-with-exitiframe.js +23 -0
- package/build/cjs/auth/helpers/reject-bot-request.js +23 -0
- package/build/cjs/auth/helpers/validate-session-token.js +25 -0
- package/build/cjs/auth/login/login.js +54 -0
- package/build/cjs/auth/public/authenticate.js +32 -0
- package/build/cjs/auth/webhooks/authenticate.js +68 -0
- package/build/cjs/auth/webhooks/register.js +40 -0
- package/build/cjs/billing/cancel.js +40 -0
- package/build/cjs/billing/request.js +83 -0
- package/build/cjs/billing/require.js +47 -0
- package/build/cjs/index.js +140 -0
- package/build/cjs/override-logger.js +55 -0
- package/build/cjs/types.js +25 -0
- package/build/cjs/version.js +7 -0
- package/build/ts/adapters/node/index.d.ts +2 -0
- package/build/ts/adapters/node/index.d.ts.map +1 -0
- package/build/ts/adapters/node/index.js +10 -0
- package/build/ts/adapters/node/index.js.map +1 -0
- package/build/ts/auth/admin/authenticate.d.ts +32 -0
- package/build/ts/auth/admin/authenticate.d.ts.map +1 -0
- package/build/ts/auth/admin/authenticate.js +571 -0
- package/build/ts/auth/admin/authenticate.js.map +1 -0
- package/build/ts/auth/admin/graphql-client.d.ts +22 -0
- package/build/ts/auth/admin/graphql-client.d.ts.map +1 -0
- package/build/ts/auth/admin/graphql-client.js +53 -0
- package/build/ts/auth/admin/graphql-client.js.map +1 -0
- package/build/ts/auth/admin/rest-client.d.ts +34 -0
- package/build/ts/auth/admin/rest-client.d.ts.map +1 -0
- package/build/ts/auth/admin/rest-client.js +134 -0
- package/build/ts/auth/admin/rest-client.js.map +1 -0
- package/build/ts/auth/admin/types.d.ts +100 -0
- package/build/ts/auth/admin/types.d.ts.map +1 -0
- package/build/ts/auth/admin/types.js +3 -0
- package/build/ts/auth/admin/types.js.map +1 -0
- package/build/ts/auth/const.d.ts +9 -0
- package/build/ts/auth/const.d.ts.map +1 -0
- package/build/ts/auth/const.js +12 -0
- package/build/ts/auth/const.js.map +1 -0
- package/build/ts/auth/helpers/add-response-headers.d.ts +5 -0
- package/build/ts/auth/helpers/add-response-headers.d.ts.map +1 -0
- package/build/ts/auth/helpers/add-response-headers.js +60 -0
- package/build/ts/auth/helpers/add-response-headers.js.map +1 -0
- package/build/ts/auth/helpers/begin-auth.d.ts +3 -0
- package/build/ts/auth/helpers/begin-auth.d.ts.map +1 -0
- package/build/ts/auth/helpers/begin-auth.js +24 -0
- package/build/ts/auth/helpers/begin-auth.js.map +1 -0
- package/build/ts/auth/helpers/get-session-token-header.d.ts +2 -0
- package/build/ts/auth/helpers/get-session-token-header.d.ts.map +1 -0
- package/build/ts/auth/helpers/get-session-token-header.js +9 -0
- package/build/ts/auth/helpers/get-session-token-header.js.map +1 -0
- package/build/ts/auth/helpers/handle-client-error.d.ts +11 -0
- package/build/ts/auth/helpers/handle-client-error.d.ts.map +1 -0
- package/build/ts/auth/helpers/handle-client-error.js +40 -0
- package/build/ts/auth/helpers/handle-client-error.js.map +1 -0
- package/build/ts/auth/helpers/index.d.ts +8 -0
- package/build/ts/auth/helpers/index.d.ts.map +1 -0
- package/build/ts/auth/helpers/index.js +18 -0
- package/build/ts/auth/helpers/index.js.map +1 -0
- package/build/ts/auth/helpers/redirect-to-auth-page.d.ts +3 -0
- package/build/ts/auth/helpers/redirect-to-auth-page.d.ts.map +1 -0
- package/build/ts/auth/helpers/redirect-to-auth-page.js +33 -0
- package/build/ts/auth/helpers/redirect-to-auth-page.js.map +1 -0
- package/build/ts/auth/helpers/redirect-with-app-bridge-headers.d.ts +4 -0
- package/build/ts/auth/helpers/redirect-with-app-bridge-headers.d.ts.map +1 -0
- package/build/ts/auth/helpers/redirect-with-app-bridge-headers.js +24 -0
- package/build/ts/auth/helpers/redirect-with-app-bridge-headers.js.map +1 -0
- package/build/ts/auth/helpers/redirect-with-exitiframe.d.ts +3 -0
- package/build/ts/auth/helpers/redirect-with-exitiframe.d.ts.map +1 -0
- package/build/ts/auth/helpers/redirect-with-exitiframe.js +18 -0
- package/build/ts/auth/helpers/redirect-with-exitiframe.js.map +1 -0
- package/build/ts/auth/helpers/reject-bot-request.d.ts +3 -0
- package/build/ts/auth/helpers/reject-bot-request.d.ts.map +1 -0
- package/build/ts/auth/helpers/reject-bot-request.js +14 -0
- package/build/ts/auth/helpers/reject-bot-request.js.map +1 -0
- package/build/ts/auth/helpers/validate-session-token.d.ts +4 -0
- package/build/ts/auth/helpers/validate-session-token.d.ts.map +1 -0
- package/build/ts/auth/helpers/validate-session-token.js +36 -0
- package/build/ts/auth/helpers/validate-session-token.js.map +1 -0
- package/build/ts/auth/login/login.d.ts +3 -0
- package/build/ts/auth/login/login.d.ts.map +1 -0
- package/build/ts/auth/login/login.js +56 -0
- package/build/ts/auth/login/login.js.map +1 -0
- package/build/ts/auth/public/authenticate.d.ts +4 -0
- package/build/ts/auth/public/authenticate.d.ts.map +1 -0
- package/build/ts/auth/public/authenticate.js +35 -0
- package/build/ts/auth/public/authenticate.js.map +1 -0
- package/build/ts/auth/public/types.d.ts +27 -0
- package/build/ts/auth/public/types.d.ts.map +1 -0
- package/build/ts/auth/public/types.js +3 -0
- package/build/ts/auth/public/types.js.map +1 -0
- package/build/ts/auth/webhooks/authenticate.d.ts +5 -0
- package/build/ts/auth/webhooks/authenticate.d.ts.map +1 -0
- package/build/ts/auth/webhooks/authenticate.js +71 -0
- package/build/ts/auth/webhooks/authenticate.js.map +1 -0
- package/build/ts/auth/webhooks/index.d.ts +2 -0
- package/build/ts/auth/webhooks/index.d.ts.map +1 -0
- package/build/ts/auth/webhooks/index.js +6 -0
- package/build/ts/auth/webhooks/index.js.map +1 -0
- package/build/ts/auth/webhooks/register.d.ts +4 -0
- package/build/ts/auth/webhooks/register.d.ts.map +1 -0
- package/build/ts/auth/webhooks/register.js +39 -0
- package/build/ts/auth/webhooks/register.js.map +1 -0
- package/build/ts/auth/webhooks/types.d.ts +18 -0
- package/build/ts/auth/webhooks/types.d.ts.map +1 -0
- package/build/ts/auth/webhooks/types.js +3 -0
- package/build/ts/auth/webhooks/types.js.map +1 -0
- package/build/ts/billing/cancel.d.ts +5 -0
- package/build/ts/billing/cancel.d.ts.map +1 -0
- package/build/ts/billing/cancel.js +43 -0
- package/build/ts/billing/cancel.js.map +1 -0
- package/build/ts/billing/index.d.ts +4 -0
- package/build/ts/billing/index.d.ts.map +1 -0
- package/build/ts/billing/index.js +10 -0
- package/build/ts/billing/index.js.map +1 -0
- package/build/ts/billing/request.d.ts +6 -0
- package/build/ts/billing/request.d.ts.map +1 -0
- package/build/ts/billing/request.js +84 -0
- package/build/ts/billing/request.js.map +1 -0
- package/build/ts/billing/require.d.ts +6 -0
- package/build/ts/billing/require.d.ts.map +1 -0
- package/build/ts/billing/require.js +55 -0
- package/build/ts/billing/require.js.map +1 -0
- package/build/ts/billing/types.d.ts +228 -0
- package/build/ts/billing/types.d.ts.map +1 -0
- package/build/ts/billing/types.js +3 -0
- package/build/ts/billing/types.js.map +1 -0
- package/build/ts/config-types.d.ts +325 -0
- package/build/ts/config-types.d.ts.map +1 -0
- package/build/ts/config-types.js +3 -0
- package/build/ts/config-types.js.map +1 -0
- package/build/ts/index.d.ts +29 -0
- package/build/ts/index.d.ts.map +1 -0
- package/build/ts/index.js +108 -0
- package/build/ts/index.js.map +1 -0
- package/build/ts/override-logger.d.ts +3 -0
- package/build/ts/override-logger.d.ts.map +1 -0
- package/build/ts/override-logger.js +40 -0
- package/build/ts/override-logger.js.map +1 -0
- package/build/ts/types.d.ts +353 -0
- package/build/ts/types.d.ts.map +1 -0
- package/build/ts/types.js +17 -0
- package/build/ts/types.js.map +1 -0
- package/build/ts/version.d.ts +2 -0
- package/build/ts/version.d.ts.map +1 -0
- package/build/ts/version.js +5 -0
- package/build/ts/version.js.map +1 -0
- package/build/tsconfig.tsbuildinfo +1 -0
- package/package.json +65 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022-present, Shopify Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
# `@shopify/shopify-app-remix`
|
|
2
|
+
|
|
3
|
+
<!-- ![Build Status]() -->
|
|
4
|
+
|
|
5
|
+
[](LICENSE.md)
|
|
6
|
+
[](https://badge.fury.io/js/%40shopify%2Fshopify-app-remix)
|
|
7
|
+
|
|
8
|
+
This package makes it easy to use [Remix](https://remix.run/) to build Shopify apps.
|
|
9
|
+
It builds on the `@shopify/shopify-api` package and exposes a `shopifyApp` function. You can use `shopifyApp` to configure your app and then authenticate requests from Shopify.
|
|
10
|
+
|
|
11
|
+
## Requirements
|
|
12
|
+
|
|
13
|
+
To follow these usage guides, you will need to:
|
|
14
|
+
|
|
15
|
+
- have a Shopify Partner account and development store
|
|
16
|
+
- have an app already set up on your partner account
|
|
17
|
+
- have a JavaScript package manager such as [yarn](https://yarnpkg.com) installed
|
|
18
|
+
|
|
19
|
+
## Shopify CLI
|
|
20
|
+
|
|
21
|
+
The easiest way to get started with developing Shopify apps is using the [Shopify CLI](https://shopify.dev/docs/apps/tools/cli).
|
|
22
|
+
It helps you set up your environment for developing and publishing your apps and extensions.
|
|
23
|
+
|
|
24
|
+
We strongly recommend using the CLI to create and manage your apps and extensions!
|
|
25
|
+
|
|
26
|
+
## Getting started
|
|
27
|
+
|
|
28
|
+
This package works with any Remix app. If you're starting an app from scratch, you can create a brand new Remix app that uses the indie-stack:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npx create-remix@latest --template remix-run/indie-stack
|
|
32
|
+
cd ./name-of-your-app
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Now let's install this package:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm install @shopify/shopify-app-remix
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Create `app/shopify.server.js`. We will use this file to configure our Shopify app:
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
// app/shopify.server.js
|
|
45
|
+
import '@shopify/shopify-app-remix/adapters/node';
|
|
46
|
+
import {LATEST_API_VERSION, shopifyApp} from '@shopify/shopify-app-remix';
|
|
47
|
+
|
|
48
|
+
export const shopify = shopifyApp({
|
|
49
|
+
apiKey: process.env.SHOPIFY_API_KEY!,
|
|
50
|
+
apiSecretKey: process.env.SHOPIFY_API_SECRET!,
|
|
51
|
+
appUrl: process.env.SHOPIFY_APP_URL!,
|
|
52
|
+
scopes: ['read_products'],
|
|
53
|
+
apiVersion: LATEST_API_VERSION,
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
A description of these config options:
|
|
58
|
+
|
|
59
|
+
| option | description |
|
|
60
|
+
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
61
|
+
| apiKey | The Client ID for your app. Copy it from your app in the Shopify partners dashboard. This is public. |
|
|
62
|
+
| apiSecretKey | The API Secret for your app. Copy it from your app in the Shopify partners dashboard. This is private. Do not commit this. |
|
|
63
|
+
| appUrl | This is the URL for your app. |
|
|
64
|
+
| scopes | What permissions your app needs. [More information](https://shopify.dev/docs/api/usage/access-scopes). |
|
|
65
|
+
| apiVersion | What versions of the [Admin API's](https://shopify.dev/docs/api/) do you want to use. If you are creating anew app use LATEST_API_VERSION. |
|
|
66
|
+
| restResources | What version of the [Shopify Admin REST API](https://shopify.dev/docs/api/admin-rest) do you want to use. If you are creating anew app use LATEST_API_VERSION. |
|
|
67
|
+
|
|
68
|
+
This will require some environment variables. So let's create an `.env` file:
|
|
69
|
+
|
|
70
|
+
```env
|
|
71
|
+
SHOPIFY_API_KEY="[Copy from partners dashboard]"
|
|
72
|
+
SHOPIFY_API_SECRET="[Copy from partners dashboard]"
|
|
73
|
+
SHOPIFY_APP_URL="[The tunnel URL you are using to run your app]"
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
`shopifyApp` needs to reserve a [splat route](https://remix.run/docs/en/main/guides/routing#splats). The default is `apps/routes/auth/$.tsx`, but you can configure this route using the `authPathPrefix option`:
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
// app/shopify.server.js
|
|
80
|
+
import {shopifyApp} from '@shopify/shopify-app-remix';
|
|
81
|
+
|
|
82
|
+
export const shopify = shopifyApp({
|
|
83
|
+
// ...
|
|
84
|
+
authPathPrefix: '/auth',
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Now let's create the [splat route](https://remix.run/docs/en/main/guides/routing#splats) for auth. It should export a loader that uses `shopifyApp` to authenticate:
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
// app/routes/auth/$.tsx
|
|
92
|
+
import {LoaderArgs} from '@remix-run/node';
|
|
93
|
+
|
|
94
|
+
import {shopify} from '../../shopify.server';
|
|
95
|
+
|
|
96
|
+
export async function loader({request}: LoaderArgs) {
|
|
97
|
+
await shopify.authenticate.admin(request);
|
|
98
|
+
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Finally if your app is embedded (this is the default) we need to setup [App Bridge](https://shopify.dev/docs/apps/tools/app-bridge) in `root.tsx.`. To do this pass the `process.env.SHOPIFY_API_KEY` to the frontend via the loader and load App Bridge from the CDN in the document head.
|
|
104
|
+
|
|
105
|
+
Here is an example:
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
// root.tsx
|
|
109
|
+
export async function loader() {
|
|
110
|
+
return json({
|
|
111
|
+
apiKey: process.env.SHOPIFY_API_KEY,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export default function App() {
|
|
116
|
+
const {apiKey} = useLoaderData<typeof loader>();
|
|
117
|
+
|
|
118
|
+
return (
|
|
119
|
+
<html>
|
|
120
|
+
<head>
|
|
121
|
+
<Meta />
|
|
122
|
+
<Links />
|
|
123
|
+
{/* App Bridge must be loaded from the CDN at the head */}
|
|
124
|
+
<script
|
|
125
|
+
src="https://cdn.shopify.com/shopifycloud/app-bridge-next.js"
|
|
126
|
+
data-api-key={apiKey}
|
|
127
|
+
/>
|
|
128
|
+
</head>
|
|
129
|
+
<body>//</body>
|
|
130
|
+
</html>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
> **Note**: This version of App Bridge must be loaded from the CDN, in the document head.
|
|
136
|
+
|
|
137
|
+
Now that your app is ready to respond to requests, it will also need to add the required `Content-Security-Policy` header directives, as per [our documentation](https://shopify.dev/docs/apps/store/security/iframe-protection).
|
|
138
|
+
To do that, this package provides the `shopify.addResponseHeaders` method.
|
|
139
|
+
|
|
140
|
+
You should return these headers from any endpoint that renders HTML in your app.
|
|
141
|
+
Most likely you'll want to add this to every HTML response by updating the entry.server.tsx file:
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
// entry.server.tsx
|
|
145
|
+
import {shopify} from '~/shopify.server';
|
|
146
|
+
|
|
147
|
+
export default function handleRequest(
|
|
148
|
+
request: Request,
|
|
149
|
+
responseStatusCode: number,
|
|
150
|
+
responseHeaders: Headers,
|
|
151
|
+
remixContext: EntryContext,
|
|
152
|
+
) {
|
|
153
|
+
const markup = renderToString(
|
|
154
|
+
<RemixServer context={remixContext} url={request.url} />,
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
responseHeaders.set('Content-Type', 'text/html');
|
|
158
|
+
shopify.addResponseHeaders(request, responseHeaders);
|
|
159
|
+
|
|
160
|
+
return new Response('<!DOCTYPE html>' + markup, {
|
|
161
|
+
status: responseStatusCode,
|
|
162
|
+
headers: responseHeaders,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
If you don't want to add this to every HTML request, you can call it in individual loaders, but you should only do this if you have a good reason not to include the headers in every HTML request.
|
|
168
|
+
|
|
169
|
+
## Setting up for your runtime
|
|
170
|
+
|
|
171
|
+
By default, this package will work with the runtimes supported by [Remix adapters](https://remix.run/docs/en/1.17.1/other-api/adapter#official-adapters) because it relies on the same [Web Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API).
|
|
172
|
+
|
|
173
|
+
Since Node.js doesn't fully implement that API, apps will need to import an extra adapter to set it up before using this package's exports.
|
|
174
|
+
|
|
175
|
+
In the [Getting Started](#getting-started) section above, you'll notice that the example runs
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
import '@shopify/shopify-app-remix/adapters/node';
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
before calling `shopifyApp`.
|
|
182
|
+
If you're running on a runtime other than Node, you can simply omit that line.
|
|
183
|
+
|
|
184
|
+
## Loading your app in admin
|
|
185
|
+
|
|
186
|
+
To load your app within the Shopify Admin, you need to:
|
|
187
|
+
|
|
188
|
+
1. Update your app's URL in your Partners Dashboard app setup page to `http://localhost:8080`
|
|
189
|
+
1. Update your app's callback URL to `http://localhost:8080/api/auth/callback` in that same page
|
|
190
|
+
1. Go to **Test your app** in Partners Dashboard and select your development store
|
|
191
|
+
|
|
192
|
+
## Authenticating admin requests
|
|
193
|
+
|
|
194
|
+
`shopifyApp` provides methods for authenticating admin requests. To authenticate admin requests you can call `shopify.authenticate.admin(request)` in a loader or an action:
|
|
195
|
+
|
|
196
|
+
```ts
|
|
197
|
+
// app/routes/**/*.tsx
|
|
198
|
+
export const loader = async ({request}: LoaderArgs) => {
|
|
199
|
+
await shopify.authenticate.admin(request);
|
|
200
|
+
|
|
201
|
+
return null;
|
|
202
|
+
};
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
If there is a session for this user, this loader will return null. If there is no session for the user, the loader will throw the appropriate redirect Response.
|
|
206
|
+
|
|
207
|
+
### Using the Shopify admin GraphQL API
|
|
208
|
+
|
|
209
|
+
To access the [Shopify Admin GraphQL API](https://shopify.dev/docs/api/admin-graphql) pass a request from a loader or an action to `shopify.authenticate.admin`.
|
|
210
|
+
This will either redirect the merchant to install your app or it will give you access to API functions.
|
|
211
|
+
E.g:
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
// routes/**/*.tsx
|
|
215
|
+
import {shopify} from '../shopify.server';
|
|
216
|
+
import {ActionArgs, json} from '@remix-run/node';
|
|
217
|
+
|
|
218
|
+
export async function action({request}: ActionArgs) {
|
|
219
|
+
const {admin} = await shopify.authenticate.admin(request);
|
|
220
|
+
|
|
221
|
+
const response = await admin.graphql(
|
|
222
|
+
`#graphql
|
|
223
|
+
mutation populateProduct($input: ProductInput!) {
|
|
224
|
+
productCreate(input: $input) {
|
|
225
|
+
product {
|
|
226
|
+
id
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}`,
|
|
230
|
+
{
|
|
231
|
+
variables: {
|
|
232
|
+
input: {
|
|
233
|
+
title: 'New product',
|
|
234
|
+
variants: [{price: 100}],
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
);
|
|
239
|
+
const parsedResponse = await response.json();
|
|
240
|
+
|
|
241
|
+
return json({data: parsedResponse.data});
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Using the Shopify admin REST API
|
|
246
|
+
|
|
247
|
+
`shopify.authenticate.admin` can returns methods for interacting with [Shopify Admin REST API](https://shopify.dev/docs/api/admin-rest). To access the [Shopify Admin REST API](https://shopify.dev/docs/api/admin-rest) first configure `shopifyApp` with the REST resources you would like to use:
|
|
248
|
+
|
|
249
|
+
```ts
|
|
250
|
+
// app/routes/**/*.tsx
|
|
251
|
+
import {restResources} from '@shopify/shopify-api/rest/admin/2023-04';
|
|
252
|
+
|
|
253
|
+
export const shopify = shopifyApp({
|
|
254
|
+
restResources,
|
|
255
|
+
// ...etc
|
|
256
|
+
});
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Next pass a request to `shopify.authenticate.admin` in a loader or an action. This will either redirect the merchant to install your app or it will give you access to API functions. E.g:
|
|
260
|
+
|
|
261
|
+
```ts
|
|
262
|
+
// app/routes/**/*.tsx
|
|
263
|
+
export const loader = async ({request}: LoaderArgs) => {
|
|
264
|
+
const {admin, session} = await shopify.authenticate.admin(request);
|
|
265
|
+
const data = await admin.rest.resources.Product.count({session});
|
|
266
|
+
|
|
267
|
+
return json(data);
|
|
268
|
+
};
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## Authenticating webhook requests
|
|
272
|
+
|
|
273
|
+
Your app must respond to [mandatory webhook topics](https://shopify.dev/docs/apps/webhooks/configuration/mandatory-webhooks). In addition, your app can register [optional webhook topics](https://shopify.dev/docs/api/admin-rest/2023-04/resources/webhook#event-topics).
|
|
274
|
+
|
|
275
|
+
To setup webhooks first we need to configure `shopifyApp` with 2 pieces:
|
|
276
|
+
|
|
277
|
+
1. The webhooks you want to subscribe to. In this example we subscribe to the `APP_UNINSTALLED` topic.
|
|
278
|
+
2. The code to register the `APP_UNINSTALLED` topic after a merchant installs you app. Here `shopifyApp` provides an `afterAuth` hook.
|
|
279
|
+
|
|
280
|
+
```ts
|
|
281
|
+
// shopify.server.js
|
|
282
|
+
import {shopifyApp, DeliveryMethod} from '@shopify/shopify-app-remix';
|
|
283
|
+
|
|
284
|
+
export const shopify = shopifyApp({
|
|
285
|
+
apiKey: '1707326264fde5037c658n120626ce3f',
|
|
286
|
+
// ...etc
|
|
287
|
+
webhooks: {
|
|
288
|
+
APP_UNINSTALLED: {
|
|
289
|
+
deliveryMethod: DeliveryMethod.Http,
|
|
290
|
+
callbackUrl: '/webhooks',
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
hooks: {
|
|
294
|
+
afterAuth: async ({session}) => {
|
|
295
|
+
shopify.registerWebhooks({session});
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
});
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
Next you must add a route for each `callbackUrl` you pass. It should use the `shopify.authenticate.webhook` function to authenticate the request. For example:
|
|
302
|
+
|
|
303
|
+
To do this, your app must authenticate the request.
|
|
304
|
+
|
|
305
|
+
```ts
|
|
306
|
+
// routes/webhooks.tsx
|
|
307
|
+
import {ActionArgs} from '@remix-run/node';
|
|
308
|
+
|
|
309
|
+
import {shopify} from '../shopify.server';
|
|
310
|
+
import db from '../db.server';
|
|
311
|
+
|
|
312
|
+
export const action = async ({request}: ActionArgs) => {
|
|
313
|
+
const {topic, shop} = await shopify.authenticate.webhook(request);
|
|
314
|
+
|
|
315
|
+
switch (topic) {
|
|
316
|
+
case 'APP_UNINSTALLED':
|
|
317
|
+
await db.session.deleteMany({where: {shop}});
|
|
318
|
+
break;
|
|
319
|
+
case 'CUSTOMERS_DATA_REQUEST':
|
|
320
|
+
case 'CUSTOMERS_REDACT':
|
|
321
|
+
case 'SHOP_REDACT':
|
|
322
|
+
default:
|
|
323
|
+
throw new Response('Unhandled webhook topic', {status: 404});
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
throw new Response();
|
|
327
|
+
};
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
## Authenticating public requests
|
|
331
|
+
|
|
332
|
+
Your Remix app may need to authenticate requests coming from a public context. An example of this would be a checkout extension. Here is how:
|
|
333
|
+
|
|
334
|
+
```ts
|
|
335
|
+
// e.g: routes/api/public/notes.tsx
|
|
336
|
+
import {shopify} from '../shopify.server';
|
|
337
|
+
import {LoaderArgs, json} from '@remix-run/node';
|
|
338
|
+
import {getNotes} from '~/models/notes';
|
|
339
|
+
|
|
340
|
+
export const loader = async ({request}: LoaderArgs) => {
|
|
341
|
+
const {sessionToken} = await shopify.authenticate.public(request);
|
|
342
|
+
|
|
343
|
+
// E.g: Get notes using the shops admin domain
|
|
344
|
+
return json(await getNotes(sessionToken.iss));
|
|
345
|
+
};
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
This can be useful if your app exposes checkout or theme extensions and those extensions need to access data from your app.
|
|
349
|
+
|
|
350
|
+
## Session Storage
|
|
351
|
+
|
|
352
|
+
By default `shopifyApp` uses [@shopify/shopify-app-session-storage-sqlite](https://github.com/Shopify/shopify-app-js/tree/main/packages/shopify-app-session-storage-sqlite) to store sessions. You can change this by passing a different Session Adaptor to `shopifyApp`. To make this easy Shopify offer's [7 production ready session adaptors](https://github.com/Shopify/shopify-app-js/tree/release-candidate/packages)
|
|
353
|
+
|
|
354
|
+
In this example we'll swap the default session adaptor for [Prisma](https://www.prisma.io/).
|
|
355
|
+
|
|
356
|
+
Let's pass the [Prisma app session storage](https://github.com/Shopify/shopify-app-js/blob/main/packages/shopify-app-session-storage-prisma/README.md) adaptor to `shopifyApp`:
|
|
357
|
+
|
|
358
|
+
```ts
|
|
359
|
+
// app/shopify.server.js
|
|
360
|
+
import {shopifyApp} from '@shopify/shopify-app-remix';
|
|
361
|
+
import {PrismaSessionStorage} from '@shopify/shopify-app-session-storage-prisma';
|
|
362
|
+
import {PrismaClient} from '@prisma/client';
|
|
363
|
+
|
|
364
|
+
const prisma = new PrismaClient();
|
|
365
|
+
const storage = new PrismaSessionStorage(prisma);
|
|
366
|
+
|
|
367
|
+
const shopify = shopifyApp({
|
|
368
|
+
sessionStorage: storage,
|
|
369
|
+
// ...
|
|
370
|
+
});
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
Note that this requires a `schema.prisma` file as defined in the README for [Prisma app session storage](https://github.com/Shopify/shopify-app-js/tree/main/packages/shopify-app-session-storage-prisma)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var crypto = require('crypto');
|
|
4
|
+
var runtime = require('@shopify/shopify-api/runtime');
|
|
5
|
+
|
|
6
|
+
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
7
|
+
|
|
8
|
+
var crypto__default = /*#__PURE__*/_interopDefaultLegacy(crypto);
|
|
9
|
+
|
|
10
|
+
runtime.setCrypto(crypto__default["default"]);
|
|
11
|
+
runtime.setAbstractRuntimeString(() => {
|
|
12
|
+
return `Remix (Node)`;
|
|
13
|
+
});
|