shopify-nuxt 0.0.2 → 0.0.3
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/README.md +293 -23
- package/dist/module.json +1 -1
- package/dist/module.mjs +23 -5
- package/dist/runtime/components/polaris/ShButton.d.vue.ts +1 -1
- package/dist/runtime/components/polaris/ShButton.vue.d.ts +1 -1
- package/dist/runtime/components/polaris/ShCheckbox.d.vue.ts +2 -2
- package/dist/runtime/components/polaris/ShCheckbox.vue.d.ts +2 -2
- package/dist/runtime/components/polaris/ShChoiceList.d.vue.ts +2 -2
- package/dist/runtime/components/polaris/ShChoiceList.vue.d.ts +2 -2
- package/dist/runtime/components/polaris/ShColorField.d.vue.ts +2 -2
- package/dist/runtime/components/polaris/ShColorField.vue.d.ts +2 -2
- package/dist/runtime/components/polaris/ShColorPicker.d.vue.ts +2 -2
- package/dist/runtime/components/polaris/ShColorPicker.vue.d.ts +2 -2
- package/dist/runtime/components/polaris/ShDateField.d.vue.ts +2 -2
- package/dist/runtime/components/polaris/ShDateField.vue.d.ts +2 -2
- package/dist/runtime/components/polaris/ShDatePicker.d.vue.ts +2 -2
- package/dist/runtime/components/polaris/ShDatePicker.vue.d.ts +2 -2
- package/dist/runtime/components/polaris/ShDropZone.d.vue.ts +2 -2
- package/dist/runtime/components/polaris/ShDropZone.vue.d.ts +2 -2
- package/dist/runtime/components/polaris/ShEmailField.d.vue.ts +2 -2
- package/dist/runtime/components/polaris/ShEmailField.vue.d.ts +2 -2
- package/dist/runtime/components/polaris/ShMoneyField.d.vue.ts +2 -2
- package/dist/runtime/components/polaris/ShMoneyField.vue.d.ts +2 -2
- package/dist/runtime/components/polaris/ShNumberField.d.vue.ts +2 -2
- package/dist/runtime/components/polaris/ShNumberField.vue.d.ts +2 -2
- package/dist/runtime/components/polaris/ShPasswordField.d.vue.ts +2 -2
- package/dist/runtime/components/polaris/ShPasswordField.vue.d.ts +2 -2
- package/dist/runtime/components/polaris/ShSearchField.d.vue.ts +2 -2
- package/dist/runtime/components/polaris/ShSearchField.vue.d.ts +2 -2
- package/dist/runtime/components/polaris/ShSelect.d.vue.ts +2 -2
- package/dist/runtime/components/polaris/ShSelect.vue.d.ts +2 -2
- package/dist/runtime/components/polaris/ShSwitch.d.vue.ts +2 -2
- package/dist/runtime/components/polaris/ShSwitch.vue.d.ts +2 -2
- package/dist/runtime/components/polaris/ShTextArea.d.vue.ts +2 -2
- package/dist/runtime/components/polaris/ShTextArea.vue.d.ts +2 -2
- package/dist/runtime/components/polaris/ShTextField.d.vue.ts +2 -2
- package/dist/runtime/components/polaris/ShTextField.vue.d.ts +2 -2
- package/dist/runtime/components/polaris/ShUrlField.d.vue.ts +2 -2
- package/dist/runtime/components/polaris/ShUrlField.vue.d.ts +2 -2
- package/dist/runtime/composables/useAppBridge.d.ts +4 -1
- package/dist/runtime/composables/useAppBridge.js +24 -13
- package/dist/runtime/middleware/shopify-auth.js +10 -4
- package/dist/runtime/pages/auth-login.d.vue.ts +3 -0
- package/dist/runtime/pages/auth-login.vue +90 -0
- package/dist/runtime/pages/auth-login.vue.d.ts +3 -0
- package/dist/runtime/server/index.d.ts +2 -0
- package/dist/runtime/server/index.js +4 -0
- package/dist/runtime/server/plugins/shopify-defaults.d.ts +8 -0
- package/dist/runtime/server/plugins/shopify-defaults.js +12 -0
- package/dist/runtime/server/routes/auth-callback.d.ts +1 -1
- package/dist/runtime/server/routes/auth-exit-iframe.d.ts +1 -1
- package/dist/runtime/server/routes/auth-session-token.d.ts +1 -1
- package/dist/runtime/server/routes/auth.d.ts +1 -1
- package/dist/runtime/server/services/shopify.js +3 -3
- package/dist/runtime/server/utils/clients.d.ts +24 -5
- package/dist/runtime/server/utils/clients.js +21 -2
- package/dist/runtime/server/utils/helpers.js +13 -11
- package/dist/runtime/server/utils/unauthenticated-storefront.d.ts +1 -5
- package/dist/runtime/server/utils/unauthenticated-storefront.js +2 -8
- package/dist/runtime/types.d.ts +33 -2
- package/package.json +9 -5
- package/dist/runtime/plugins/polaris.d.ts +0 -1
- package/dist/runtime/plugins/polaris.js +0 -0
package/README.md
CHANGED
|
@@ -47,22 +47,34 @@ export default defineNuxtConfig({
|
|
|
47
47
|
shopify: {
|
|
48
48
|
apiKey: 'ApiKeyFromPartnersDashboard',
|
|
49
49
|
apiSecretKey: 'ApiSecretKeyFromPartnersDashboard',
|
|
50
|
-
scopes: ['read_products', 'write_products'],
|
|
51
50
|
appUrl: 'https://your-app-url.com'
|
|
52
51
|
}
|
|
53
52
|
})
|
|
54
53
|
```
|
|
55
54
|
|
|
56
|
-
|
|
55
|
+
All values can also be set via environment variables:
|
|
56
|
+
|
|
57
|
+
| Option | Env Variable |
|
|
58
|
+
| -------------- | ----------------------------- |
|
|
59
|
+
| `apiKey` | `NUXT_SHOPIFY_API_KEY` |
|
|
60
|
+
| `apiSecretKey` | `NUXT_SHOPIFY_API_SECRET_KEY` |
|
|
61
|
+
| `appUrl` | `NUXT_SHOPIFY_APP_URL` |
|
|
62
|
+
|
|
63
|
+
### Session storage
|
|
64
|
+
|
|
65
|
+
By default, the module provides an in-memory session storage (`MemorySessionStorage`) that works out of the box — no configuration needed. For production use, you should use a persistent session storage adapter.
|
|
66
|
+
|
|
67
|
+
To override the default session storage or configure lifecycle hooks, create a Nitro server plugin:
|
|
57
68
|
|
|
58
69
|
```ts
|
|
59
70
|
// server/plugins/shopify.ts
|
|
60
71
|
import { configureShopify } from '#shopify/server'
|
|
61
|
-
import {
|
|
72
|
+
import { PrismaSessionStorage } from '@shopify/shopify-app-session-storage-prisma'
|
|
73
|
+
import { prisma } from '~/server/utils/prisma'
|
|
62
74
|
|
|
63
75
|
export default defineNitroPlugin(() => {
|
|
64
76
|
configureShopify({
|
|
65
|
-
sessionStorage: new
|
|
77
|
+
sessionStorage: new PrismaSessionStorage(prisma),
|
|
66
78
|
hooks: {
|
|
67
79
|
afterAuth: async ({ session, admin }) => {
|
|
68
80
|
// Register webhooks, seed data, etc.
|
|
@@ -72,14 +84,30 @@ export default defineNitroPlugin(() => {
|
|
|
72
84
|
})
|
|
73
85
|
```
|
|
74
86
|
|
|
75
|
-
|
|
87
|
+
Any config passed to `configureShopify()` is merged with the defaults — you only need to specify what you want to override.
|
|
88
|
+
|
|
89
|
+
### Module options
|
|
90
|
+
|
|
91
|
+
| Option | Type | Default | Description |
|
|
92
|
+
| ----------------- | ----------------- | ---------------- | ----------------------------------------------------------------------- |
|
|
93
|
+
| `apiKey` | `string` | — | Your Shopify API key from the Partners dashboard |
|
|
94
|
+
| `apiSecretKey` | `string` | — | Your Shopify API secret key |
|
|
95
|
+
| `scopes` | `string[]` | `[]` | OAuth scopes (optional — Shopify reads from `shopify.app.toml`) |
|
|
96
|
+
| `appUrl` | `string` | — | Your app's public URL (tunnel URL in development) |
|
|
97
|
+
| `apiVersion` | `ApiVersion` | Latest stable | The Shopify API version to use |
|
|
98
|
+
| `authPathPrefix` | `string` | `/_shopify/auth` | URL prefix for OAuth endpoints |
|
|
99
|
+
| `distribution` | `AppDistribution` | `app_store` | App distribution type (`app_store`, `single_merchant`, `shopify_admin`) |
|
|
100
|
+
| `useOnlineTokens` | `boolean` | `false` | Use online (per-user) tokens in addition to offline (per-shop) tokens |
|
|
101
|
+
| `authPage` | `string \| false` | built-in page | Custom auth page component path, or `false` to disable |
|
|
102
|
+
|
|
103
|
+
## Authenticating admin requests
|
|
76
104
|
|
|
77
|
-
Use `useShopifyAdmin()` in your server API routes to authenticate requests from the Shopify admin
|
|
105
|
+
Use `useShopifyAdmin()` in your server API routes to authenticate requests from the Shopify admin. The returned `admin` object provides **typed GraphQL** and REST clients powered by `@shopify/admin-api-client`:
|
|
78
106
|
|
|
79
107
|
```ts
|
|
80
108
|
// server/api/products.ts
|
|
81
109
|
export default defineEventHandler(async (event) => {
|
|
82
|
-
const { admin } = await useShopifyAdmin(event)
|
|
110
|
+
const { admin, session } = await useShopifyAdmin(event)
|
|
83
111
|
|
|
84
112
|
const response = await admin.graphql(`{
|
|
85
113
|
products(first: 5) {
|
|
@@ -92,13 +120,57 @@ export default defineEventHandler(async (event) => {
|
|
|
92
120
|
}
|
|
93
121
|
}`)
|
|
94
122
|
|
|
95
|
-
return response
|
|
123
|
+
return await response.json()
|
|
124
|
+
})
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
#### GraphQL with variables
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
const response = await admin.graphql(
|
|
131
|
+
`#graphql
|
|
132
|
+
mutation populateProduct($input: ProductInput!) {
|
|
133
|
+
productCreate(input: $input) {
|
|
134
|
+
product {
|
|
135
|
+
id
|
|
136
|
+
title
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}`,
|
|
140
|
+
{
|
|
141
|
+
variables: {
|
|
142
|
+
input: { title: 'New Product' }
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
const { data } = await response.json()
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
#### REST API
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
const products = await admin.rest.get({ path: 'products' })
|
|
154
|
+
await admin.rest.post({
|
|
155
|
+
path: 'products',
|
|
156
|
+
data: { product: { title: 'New Product' } }
|
|
96
157
|
})
|
|
97
158
|
```
|
|
98
159
|
|
|
99
|
-
|
|
160
|
+
#### Full `AdminContext` return type
|
|
161
|
+
|
|
162
|
+
| Property | Type | Description |
|
|
163
|
+
| -------------- | -------------------------- | -------------------------------------------------- |
|
|
164
|
+
| `session` | `Session` | The authenticated Shopify session |
|
|
165
|
+
| `admin` | `AdminApiContext` | GraphQL and REST API clients |
|
|
166
|
+
| `sessionToken` | `JwtPayload \| undefined` | Decoded session token (embedded apps only) |
|
|
167
|
+
| `billing` | `BillingContext` | Helpers for `require()`, `check()`, `request()` |
|
|
168
|
+
| `cors` | `(response) => Response` | Add CORS headers to a response |
|
|
169
|
+
| `redirect` | `(url, init?) => Response` | Redirect helper that works inside embedded iframes |
|
|
170
|
+
|
|
171
|
+
## Handling webhooks
|
|
100
172
|
|
|
101
|
-
Use `useShopifyWebhook()` to validate and process incoming webhooks:
|
|
173
|
+
Use `useShopifyWebhook()` to validate and process incoming webhooks. HMAC verification is handled automatically:
|
|
102
174
|
|
|
103
175
|
```ts
|
|
104
176
|
// server/api/webhooks.ts
|
|
@@ -118,7 +190,81 @@ export default defineEventHandler(async (event) => {
|
|
|
118
190
|
})
|
|
119
191
|
```
|
|
120
192
|
|
|
121
|
-
|
|
193
|
+
#### Registering webhooks programmatically
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
// server/plugins/shopify.ts
|
|
197
|
+
import { configureShopify, registerShopifyWebhooks } from '#shopify/server'
|
|
198
|
+
|
|
199
|
+
export default defineNitroPlugin(() => {
|
|
200
|
+
configureShopify({
|
|
201
|
+
hooks: {
|
|
202
|
+
afterAuth: async ({ session }) => {
|
|
203
|
+
await registerShopifyWebhooks(session)
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
})
|
|
207
|
+
})
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
> **Tip**: For most apps, registering webhooks via `shopify.app.toml` is sufficient. Use `registerShopifyWebhooks()` only when you need dynamic, per-shop webhook registration.
|
|
211
|
+
|
|
212
|
+
## Authenticating other request types
|
|
213
|
+
|
|
214
|
+
### Shopify Flow
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
// server/api/flow.ts
|
|
218
|
+
export default defineEventHandler(async (event) => {
|
|
219
|
+
const { session, admin, payload } = await useShopifyFlow(event)
|
|
220
|
+
// Handle Flow trigger/action
|
|
221
|
+
})
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Public requests (checkout extensions, etc.)
|
|
225
|
+
|
|
226
|
+
```ts
|
|
227
|
+
// server/api/public/widget.ts
|
|
228
|
+
export default defineEventHandler(async (event) => {
|
|
229
|
+
const { sessionToken, cors } = await useShopifyPublic(event)
|
|
230
|
+
// sessionToken contains the decoded JWT payload
|
|
231
|
+
// Use cors() to wrap your response with CORS headers
|
|
232
|
+
})
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Unauthenticated access (background jobs)
|
|
236
|
+
|
|
237
|
+
For accessing the Shopify API without an incoming request (cron jobs, background tasks):
|
|
238
|
+
|
|
239
|
+
```ts
|
|
240
|
+
// server/api/cron/sync.ts
|
|
241
|
+
export default defineEventHandler(async () => {
|
|
242
|
+
const { admin } = await useShopifyUnauthenticatedAdmin(
|
|
243
|
+
'my-shop.myshopify.com'
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
const response = await admin.graphql(`{
|
|
247
|
+
products(first: 10) { edges { node { id title } } }
|
|
248
|
+
}`)
|
|
249
|
+
|
|
250
|
+
return await response.json()
|
|
251
|
+
})
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
```ts
|
|
255
|
+
// Storefront API access
|
|
256
|
+
const { storefront } = await useShopifyUnauthenticatedStorefront(
|
|
257
|
+
'my-shop.myshopify.com'
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
const response = await storefront.graphql(`{
|
|
261
|
+
products(first: 10) { edges { node { id title } } }
|
|
262
|
+
}`)
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
Both the admin and storefront GraphQL clients are typed via `@shopify/admin-api-client` and `@shopify/storefront-api-client` respectively.
|
|
266
|
+
|
|
267
|
+
## Using App Bridge on the client
|
|
122
268
|
|
|
123
269
|
The module automatically injects the Shopify App Bridge CDN script and provides typed access via composables:
|
|
124
270
|
|
|
@@ -127,15 +273,32 @@ The module automatically injects the Shopify App Bridge CDN script and provides
|
|
|
127
273
|
// Access the App Bridge instance (typed as ShopifyGlobal)
|
|
128
274
|
const shopify = useAppBridge()
|
|
129
275
|
|
|
130
|
-
//
|
|
276
|
+
// Get the current shop
|
|
277
|
+
const shop = shopify.config.shop
|
|
278
|
+
</script>
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### Authenticated fetch
|
|
282
|
+
|
|
283
|
+
Use `useShopifyFetch()` for client-side API calls that automatically include the session token:
|
|
284
|
+
|
|
285
|
+
```vue
|
|
286
|
+
<script setup>
|
|
131
287
|
const shopifyFetch = useShopifyFetch()
|
|
132
|
-
|
|
288
|
+
|
|
289
|
+
const { data: products } = await useAsyncData(
|
|
290
|
+
'products',
|
|
291
|
+
() => shopifyFetch('/api/products'),
|
|
292
|
+
{ server: false }
|
|
293
|
+
)
|
|
133
294
|
</script>
|
|
134
295
|
```
|
|
135
296
|
|
|
136
|
-
|
|
297
|
+
> **Important**: Always use `server: false` with `useShopifyFetch()` — session tokens are only available on the client side within the Shopify admin iframe.
|
|
137
298
|
|
|
138
|
-
|
|
299
|
+
## Using Polaris components
|
|
300
|
+
|
|
301
|
+
This module provides Vue wrapper components for [Shopify Polaris web components](https://shopify.dev/docs/api/app-home/web-components). Use the `Sh`-prefixed wrappers instead of the raw `s-*` web components — they provide typed props with autocomplete, v-model support for form components, and work seamlessly with Vue's reactivity system.
|
|
139
302
|
|
|
140
303
|
```vue
|
|
141
304
|
<template>
|
|
@@ -150,17 +313,57 @@ This module provides Vue wrapper components for [Shopify Polaris web components]
|
|
|
150
313
|
</ShBanner>
|
|
151
314
|
|
|
152
315
|
<ShTextField
|
|
316
|
+
v-model="title"
|
|
153
317
|
label="Product title"
|
|
154
|
-
:value="title"
|
|
155
318
|
placeholder="Enter product title"
|
|
156
319
|
/>
|
|
320
|
+
|
|
321
|
+
<ShSelect
|
|
322
|
+
v-model="status"
|
|
323
|
+
label="Status"
|
|
324
|
+
:options="[
|
|
325
|
+
{ label: 'Active', value: 'active' },
|
|
326
|
+
{ label: 'Draft', value: 'draft' }
|
|
327
|
+
]"
|
|
328
|
+
/>
|
|
157
329
|
</ShPage>
|
|
158
330
|
</template>
|
|
159
331
|
```
|
|
160
332
|
|
|
161
333
|
All `Sh*` components are auto-imported — no manual imports needed. Each component maps directly to its Polaris web component counterpart (e.g., `<ShButton>` → `<s-button>`, `<ShTextField>` → `<s-text-field>`).
|
|
162
334
|
|
|
163
|
-
###
|
|
335
|
+
### Available components
|
|
336
|
+
|
|
337
|
+
Layout & structure: `ShPage`, `ShBox`, `ShStack`, `ShGrid`, `ShGridItem`, `ShSection`, `ShDivider`
|
|
338
|
+
|
|
339
|
+
Actions: `ShButton`, `ShButtonGroup`, `ShClickable`, `ShLink`
|
|
340
|
+
|
|
341
|
+
Forms: `ShTextField`, `ShNumberField`, `ShEmailField`, `ShPasswordField`, `ShUrlField`, `ShMoneyField`, `ShColorField`, `ShDateField`, `ShTextArea`, `ShSelect`, `ShCheckbox`, `ShSwitch`, `ShChoiceList`, `ShChoice`, `ShSearchField`, `ShDropZone`, `ShColorPicker`, `ShDatePicker`
|
|
342
|
+
|
|
343
|
+
Feedback: `ShBanner`, `ShBadge`, `ShSpinner`, `ShTooltip`
|
|
344
|
+
|
|
345
|
+
Navigation: `ShAppNav`, `ShMenu`, `ShOption`, `ShOptionGroup`, `ShPopover`
|
|
346
|
+
|
|
347
|
+
Data: `ShTable`, `ShTableHeader`, `ShTableHeaderRow`, `ShTableBody`, `ShTableRow`, `ShTableCell`
|
|
348
|
+
|
|
349
|
+
Content: `ShText`, `ShHeading`, `ShParagraph`, `ShIcon`, `ShImage`, `ShThumbnail`, `ShAvatar`, `ShChip`, `ShClickableChip`, `ShListItem`, `ShOrderedList`, `ShUnorderedList`
|
|
350
|
+
|
|
351
|
+
Other: `ShModal`, `ShQueryContainer`
|
|
352
|
+
|
|
353
|
+
## OAuth routes
|
|
354
|
+
|
|
355
|
+
The module automatically registers these routes:
|
|
356
|
+
|
|
357
|
+
| Route | Purpose |
|
|
358
|
+
| ---------------------------------- | -------------------------------------- |
|
|
359
|
+
| `GET /_shopify/auth` | Start the OAuth flow |
|
|
360
|
+
| `GET /_shopify/auth/callback` | Handle the OAuth callback from Shopify |
|
|
361
|
+
| `GET /_shopify/auth/exit-iframe` | App Bridge iframe escape page |
|
|
362
|
+
| `GET /_shopify/auth/session-token` | Session token bounce page |
|
|
363
|
+
|
|
364
|
+
The prefix `/_shopify/auth` is configurable via the `authPathPrefix` option.
|
|
365
|
+
|
|
366
|
+
## Loading your app in Shopify Admin
|
|
164
367
|
|
|
165
368
|
To load your app within the Shopify Admin, you need to:
|
|
166
369
|
|
|
@@ -175,11 +378,15 @@ To load your app within the Shopify Admin, you need to:
|
|
|
175
378
|
| **Authentication** | OAuth flow, session tokens, token exchange — all handled automatically |
|
|
176
379
|
| **App Bridge** | CDN-based App Bridge with full TypeScript types via `@shopify/app-bridge-types` |
|
|
177
380
|
| **Polaris** | Vue wrapper components (`Sh*`) for all Polaris web components with typed props |
|
|
381
|
+
| **Typed GraphQL** | Admin and Storefront API clients typed via `@shopify/admin-api-client` |
|
|
178
382
|
| **Webhooks** | HMAC validation, payload parsing, and webhook registration |
|
|
179
383
|
| **Admin API** | GraphQL and REST clients with automatic session management |
|
|
384
|
+
| **Storefront API** | Typed GraphQL client for Storefront API via `@shopify/storefront-api-client` |
|
|
180
385
|
| **Billing** | Billing context for subscription and usage-based charges |
|
|
181
|
-
| **Session storage** |
|
|
182
|
-
| **Auto-imports** | Server utilities
|
|
386
|
+
| **Session storage** | Built-in `MemorySessionStorage` default, pluggable via `configureShopify()` |
|
|
387
|
+
| **Auto-imports** | Server utilities, client composables, and components are auto-imported |
|
|
388
|
+
| **Bot detection** | Admin auth automatically detects bots and returns 410 to avoid unnecessary auth |
|
|
389
|
+
| **CORS** | Built-in CORS helpers for public/checkout extension endpoints |
|
|
183
390
|
|
|
184
391
|
## Server utilities
|
|
185
392
|
|
|
@@ -198,14 +405,39 @@ These are auto-imported in your `server/` directory:
|
|
|
198
405
|
| `useShopifyUnauthenticatedStorefront(shop)` | Offline session storefront API access |
|
|
199
406
|
| `registerShopifyWebhooks(session)` | Register webhooks for a shop |
|
|
200
407
|
|
|
408
|
+
### `#shopify/server` exports
|
|
409
|
+
|
|
410
|
+
For use in Nitro plugins and advanced server-side configuration:
|
|
411
|
+
|
|
412
|
+
```ts
|
|
413
|
+
import {
|
|
414
|
+
configureShopify,
|
|
415
|
+
getShopifyApi,
|
|
416
|
+
getResolvedConfig,
|
|
417
|
+
getSessionStorage,
|
|
418
|
+
registerShopifyWebhooks,
|
|
419
|
+
createAdminApiContext,
|
|
420
|
+
createStorefrontApiContext
|
|
421
|
+
} from '#shopify/server'
|
|
422
|
+
|
|
423
|
+
// Types
|
|
424
|
+
import type {
|
|
425
|
+
AdminApiContext,
|
|
426
|
+
StorefrontApiContext,
|
|
427
|
+
GraphQLClient,
|
|
428
|
+
GraphQLQueryOptions,
|
|
429
|
+
GraphQLResponse
|
|
430
|
+
} from '#shopify/server'
|
|
431
|
+
```
|
|
432
|
+
|
|
201
433
|
## Client composables
|
|
202
434
|
|
|
203
435
|
These are auto-imported in your Vue components:
|
|
204
436
|
|
|
205
|
-
| Composable | Purpose
|
|
206
|
-
| ------------------- |
|
|
207
|
-
| `useAppBridge()` | Returns typed `ShopifyGlobal` from App Bridge CDN
|
|
208
|
-
| `useShopifyFetch()` | Fetch wrapper with automatic session token
|
|
437
|
+
| Composable | Purpose |
|
|
438
|
+
| ------------------- | -------------------------------------------------------------------- |
|
|
439
|
+
| `useAppBridge()` | Returns typed `ShopifyGlobal` from App Bridge CDN |
|
|
440
|
+
| `useShopifyFetch()` | Fetch wrapper with automatic session token in `Authorization` header |
|
|
209
441
|
|
|
210
442
|
## Testing your app
|
|
211
443
|
|
|
@@ -219,8 +451,38 @@ const config = testConfig()
|
|
|
219
451
|
|
|
220
452
|
// testSession() returns a mock Shopify session
|
|
221
453
|
const session = testSession()
|
|
454
|
+
|
|
455
|
+
// Both accept overrides
|
|
456
|
+
const customConfig = testConfig({ apiKey: 'custom-key' })
|
|
457
|
+
const customSession = testSession({ shop: 'custom-shop.myshopify.com' })
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
## TypeScript
|
|
461
|
+
|
|
462
|
+
The module augments Nuxt's `RuntimeConfig` types so you get full autocomplete and type safety when accessing config:
|
|
463
|
+
|
|
464
|
+
```ts
|
|
465
|
+
// Server — all Shopify config fields are typed
|
|
466
|
+
const config = useRuntimeConfig()
|
|
467
|
+
config.shopify.apiKey // string
|
|
468
|
+
config.shopify.apiSecretKey // string
|
|
469
|
+
config.shopify.scopes // string[]
|
|
470
|
+
config.shopify.appUrl // string
|
|
471
|
+
|
|
472
|
+
// Client — only public fields
|
|
473
|
+
const publicConfig = useRuntimeConfig().public
|
|
474
|
+
publicConfig.shopify.apiKey // string
|
|
475
|
+
publicConfig.shopify.authPagePath // string
|
|
476
|
+
publicConfig.shopify.authPathPrefix // string
|
|
222
477
|
```
|
|
223
478
|
|
|
479
|
+
The types are declared via module augmentation in `nuxt/schema`:
|
|
480
|
+
|
|
481
|
+
| Interface | Key | Fields |
|
|
482
|
+
| --------------------- | --------- | --------------------------------------------------------------------------------------------------------------- |
|
|
483
|
+
| `RuntimeConfig` | `shopify` | `apiKey`, `apiSecretKey`, `scopes`, `appUrl`, `apiVersion`, `authPathPrefix`, `distribution`, `useOnlineTokens` |
|
|
484
|
+
| `PublicRuntimeConfig` | `shopify` | `apiKey`, `authPagePath`, `authPathPrefix` |
|
|
485
|
+
|
|
224
486
|
## Resources
|
|
225
487
|
|
|
226
488
|
Getting started:
|
|
@@ -237,6 +499,14 @@ Shopify:
|
|
|
237
499
|
- [App extensions](https://shopify.dev/docs/apps/app-extensions/list)
|
|
238
500
|
- [Shopify Functions](https://shopify.dev/docs/api/functions)
|
|
239
501
|
|
|
502
|
+
Session storage adapters:
|
|
503
|
+
|
|
504
|
+
- [`@shopify/shopify-app-session-storage-prisma`](https://www.npmjs.com/package/@shopify/shopify-app-session-storage-prisma)
|
|
505
|
+
- [`@shopify/shopify-app-session-storage-drizzle`](https://www.npmjs.com/package/@shopify/shopify-app-session-storage-drizzle)
|
|
506
|
+
- [`@shopify/shopify-app-session-storage-redis`](https://www.npmjs.com/package/@shopify/shopify-app-session-storage-redis)
|
|
507
|
+
- [`@shopify/shopify-app-session-storage-mongodb`](https://www.npmjs.com/package/@shopify/shopify-app-session-storage-mongodb)
|
|
508
|
+
- [`@shopify/shopify-app-session-storage-memory`](https://www.npmjs.com/package/@shopify/shopify-app-session-storage-memory) (default, not for production)
|
|
509
|
+
|
|
240
510
|
## Contributing
|
|
241
511
|
|
|
242
512
|
<details>
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineNuxtModule, createResolver, addServerImportsDir, addImportsDir, addComponentsDir, addTypeTemplate, addPlugin, addServerHandler, addRouteMiddleware } from '@nuxt/kit';
|
|
1
|
+
import { defineNuxtModule, createResolver, addServerImportsDir, addImportsDir, addComponentsDir, addTypeTemplate, addPlugin, addServerHandler, addRouteMiddleware, extendPages } from '@nuxt/kit';
|
|
2
2
|
import { ApiVersion } from '@shopify/shopify-api';
|
|
3
3
|
|
|
4
4
|
const module$1 = defineNuxtModule({
|
|
@@ -9,7 +9,6 @@ const module$1 = defineNuxtModule({
|
|
|
9
9
|
defaults: {
|
|
10
10
|
apiKey: "",
|
|
11
11
|
apiSecretKey: "",
|
|
12
|
-
scopes: [],
|
|
13
12
|
appUrl: "",
|
|
14
13
|
apiVersion: ApiVersion.January26,
|
|
15
14
|
authPathPrefix: "/_shopify/auth",
|
|
@@ -21,7 +20,7 @@ const module$1 = defineNuxtModule({
|
|
|
21
20
|
nuxt.options.runtimeConfig.shopify = {
|
|
22
21
|
apiKey: options.apiKey,
|
|
23
22
|
apiSecretKey: options.apiSecretKey,
|
|
24
|
-
scopes: options.scopes,
|
|
23
|
+
scopes: options.scopes || [],
|
|
25
24
|
appUrl: options.appUrl,
|
|
26
25
|
apiVersion: options.apiVersion || ApiVersion.January26,
|
|
27
26
|
authPathPrefix: options.authPathPrefix || "/_shopify/auth",
|
|
@@ -29,7 +28,9 @@ const module$1 = defineNuxtModule({
|
|
|
29
28
|
useOnlineTokens: options.useOnlineTokens || false
|
|
30
29
|
};
|
|
31
30
|
nuxt.options.runtimeConfig.public.shopify = {
|
|
32
|
-
apiKey: options.apiKey
|
|
31
|
+
apiKey: options.apiKey,
|
|
32
|
+
authPagePath: options.authPage !== false ? "/auth" : "",
|
|
33
|
+
authPathPrefix: options.authPathPrefix || "/_shopify/auth"
|
|
33
34
|
};
|
|
34
35
|
nuxt.options.alias["#shopify/server"] = resolver.resolve("./runtime/server");
|
|
35
36
|
addServerImportsDir(resolver.resolve("./runtime/server/utils"));
|
|
@@ -115,13 +116,30 @@ export {}
|
|
|
115
116
|
path: resolver.resolve("./runtime/middleware/shopify-auth"),
|
|
116
117
|
global: false
|
|
117
118
|
});
|
|
119
|
+
if (options.authPage !== false) {
|
|
120
|
+
extendPages((pages) => {
|
|
121
|
+
pages.push({
|
|
122
|
+
name: "shopify-auth-login",
|
|
123
|
+
path: "/auth",
|
|
124
|
+
file: options.authPage || resolver.resolve("./runtime/pages/auth-login.vue")
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
nuxt.hook("nitro:config", (nitroConfig) => {
|
|
129
|
+
nitroConfig.plugins = nitroConfig.plugins || [];
|
|
130
|
+
nitroConfig.plugins.push(
|
|
131
|
+
resolver.resolve("./runtime/server/plugins/shopify-defaults")
|
|
132
|
+
);
|
|
133
|
+
});
|
|
118
134
|
nuxt.options.build.transpile.push(resolver.resolve("./runtime"));
|
|
119
135
|
nuxt.hook("nitro:config", (nitroConfig) => {
|
|
120
136
|
nitroConfig.externals = nitroConfig.externals || {};
|
|
121
137
|
nitroConfig.externals.inline = nitroConfig.externals.inline || [];
|
|
122
138
|
nitroConfig.externals.inline.push(
|
|
123
139
|
"@shopify/shopify-api",
|
|
124
|
-
"@shopify/shopify-api/adapters/node"
|
|
140
|
+
"@shopify/shopify-api/adapters/node",
|
|
141
|
+
"@shopify/shopify-app-session-storage-memory",
|
|
142
|
+
"isbot"
|
|
125
143
|
);
|
|
126
144
|
});
|
|
127
145
|
nuxt.hook("prepare:types", ({ references }) => {
|
|
@@ -22,8 +22,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {},
|
|
|
22
22
|
blur: (event: FocusEvent) => void;
|
|
23
23
|
focus: (event: FocusEvent) => void;
|
|
24
24
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
25
|
-
onBlur?: ((event: FocusEvent) => any) | undefined;
|
|
26
25
|
onClick?: ((event: MouseEvent) => any) | undefined;
|
|
26
|
+
onBlur?: ((event: FocusEvent) => any) | undefined;
|
|
27
27
|
onFocus?: ((event: FocusEvent) => any) | undefined;
|
|
28
28
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
29
29
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
@@ -22,8 +22,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {},
|
|
|
22
22
|
blur: (event: FocusEvent) => void;
|
|
23
23
|
focus: (event: FocusEvent) => void;
|
|
24
24
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
25
|
-
onBlur?: ((event: FocusEvent) => any) | undefined;
|
|
26
25
|
onClick?: ((event: MouseEvent) => any) | undefined;
|
|
26
|
+
onBlur?: ((event: FocusEvent) => any) | undefined;
|
|
27
27
|
onFocus?: ((event: FocusEvent) => any) | undefined;
|
|
28
28
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
29
29
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
@@ -18,11 +18,11 @@ type __VLS_Slots = {} & {
|
|
|
18
18
|
};
|
|
19
19
|
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
20
20
|
"update:modelValue": (v: boolean) => void;
|
|
21
|
-
change: (event: Event) => void;
|
|
22
21
|
input: (event: Event) => void;
|
|
22
|
+
change: (event: Event) => void;
|
|
23
23
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
24
|
-
onChange?: ((event: Event) => any) | undefined;
|
|
25
24
|
onInput?: ((event: Event) => any) | undefined;
|
|
25
|
+
onChange?: ((event: Event) => any) | undefined;
|
|
26
26
|
"onUpdate:modelValue"?: ((v: boolean) => any) | undefined;
|
|
27
27
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
28
28
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
@@ -18,11 +18,11 @@ type __VLS_Slots = {} & {
|
|
|
18
18
|
};
|
|
19
19
|
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
20
20
|
"update:modelValue": (v: boolean) => void;
|
|
21
|
-
change: (event: Event) => void;
|
|
22
21
|
input: (event: Event) => void;
|
|
22
|
+
change: (event: Event) => void;
|
|
23
23
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
24
|
-
onChange?: ((event: Event) => any) | undefined;
|
|
25
24
|
onInput?: ((event: Event) => any) | undefined;
|
|
25
|
+
onChange?: ((event: Event) => any) | undefined;
|
|
26
26
|
"onUpdate:modelValue"?: ((v: boolean) => any) | undefined;
|
|
27
27
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
28
28
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
@@ -14,11 +14,11 @@ type __VLS_Slots = {} & {
|
|
|
14
14
|
};
|
|
15
15
|
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
16
16
|
"update:modelValue": (v: string[]) => void;
|
|
17
|
-
change: (event: Event) => void;
|
|
18
17
|
input: (event: Event) => void;
|
|
18
|
+
change: (event: Event) => void;
|
|
19
19
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
20
|
-
onChange?: ((event: Event) => any) | undefined;
|
|
21
20
|
onInput?: ((event: Event) => any) | undefined;
|
|
21
|
+
onChange?: ((event: Event) => any) | undefined;
|
|
22
22
|
"onUpdate:modelValue"?: ((v: string[]) => any) | undefined;
|
|
23
23
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
24
24
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
@@ -14,11 +14,11 @@ type __VLS_Slots = {} & {
|
|
|
14
14
|
};
|
|
15
15
|
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
16
16
|
"update:modelValue": (v: string[]) => void;
|
|
17
|
-
change: (event: Event) => void;
|
|
18
17
|
input: (event: Event) => void;
|
|
18
|
+
change: (event: Event) => void;
|
|
19
19
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
20
|
-
onChange?: ((event: Event) => any) | undefined;
|
|
21
20
|
onInput?: ((event: Event) => any) | undefined;
|
|
21
|
+
onChange?: ((event: Event) => any) | undefined;
|
|
22
22
|
"onUpdate:modelValue"?: ((v: string[]) => any) | undefined;
|
|
23
23
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
24
24
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
@@ -21,14 +21,14 @@ type __VLS_Slots = {} & {
|
|
|
21
21
|
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
22
22
|
"update:modelValue": (v: string) => void;
|
|
23
23
|
blur: (event: InputEvent) => void;
|
|
24
|
-
change: (event: InputEvent) => void;
|
|
25
24
|
focus: (event: InputEvent) => void;
|
|
26
25
|
input: (event: InputEvent) => void;
|
|
26
|
+
change: (event: InputEvent) => void;
|
|
27
27
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
28
28
|
onBlur?: ((event: InputEvent) => any) | undefined;
|
|
29
|
-
onChange?: ((event: InputEvent) => any) | undefined;
|
|
30
29
|
onFocus?: ((event: InputEvent) => any) | undefined;
|
|
31
30
|
onInput?: ((event: InputEvent) => any) | undefined;
|
|
31
|
+
onChange?: ((event: InputEvent) => any) | undefined;
|
|
32
32
|
"onUpdate:modelValue"?: ((v: string) => any) | undefined;
|
|
33
33
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
34
34
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
@@ -21,14 +21,14 @@ type __VLS_Slots = {} & {
|
|
|
21
21
|
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
22
22
|
"update:modelValue": (v: string) => void;
|
|
23
23
|
blur: (event: InputEvent) => void;
|
|
24
|
-
change: (event: InputEvent) => void;
|
|
25
24
|
focus: (event: InputEvent) => void;
|
|
26
25
|
input: (event: InputEvent) => void;
|
|
26
|
+
change: (event: InputEvent) => void;
|
|
27
27
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
28
28
|
onBlur?: ((event: InputEvent) => any) | undefined;
|
|
29
|
-
onChange?: ((event: InputEvent) => any) | undefined;
|
|
30
29
|
onFocus?: ((event: InputEvent) => any) | undefined;
|
|
31
30
|
onInput?: ((event: InputEvent) => any) | undefined;
|
|
31
|
+
onChange?: ((event: InputEvent) => any) | undefined;
|
|
32
32
|
"onUpdate:modelValue"?: ((v: string) => any) | undefined;
|
|
33
33
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
34
34
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
@@ -10,11 +10,11 @@ type __VLS_Slots = {} & {
|
|
|
10
10
|
};
|
|
11
11
|
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
12
12
|
"update:modelValue": (v: string) => void;
|
|
13
|
-
change: (event: Event) => void;
|
|
14
13
|
input: (event: Event) => void;
|
|
14
|
+
change: (event: Event) => void;
|
|
15
15
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
16
|
-
onChange?: ((event: Event) => any) | undefined;
|
|
17
16
|
onInput?: ((event: Event) => any) | undefined;
|
|
17
|
+
onChange?: ((event: Event) => any) | undefined;
|
|
18
18
|
"onUpdate:modelValue"?: ((v: string) => any) | undefined;
|
|
19
19
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
20
20
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
@@ -10,11 +10,11 @@ type __VLS_Slots = {} & {
|
|
|
10
10
|
};
|
|
11
11
|
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
12
12
|
"update:modelValue": (v: string) => void;
|
|
13
|
-
change: (event: Event) => void;
|
|
14
13
|
input: (event: Event) => void;
|
|
14
|
+
change: (event: Event) => void;
|
|
15
15
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
16
|
-
onChange?: ((event: Event) => any) | undefined;
|
|
17
16
|
onInput?: ((event: Event) => any) | undefined;
|
|
17
|
+
onChange?: ((event: Event) => any) | undefined;
|
|
18
18
|
"onUpdate:modelValue"?: ((v: string) => any) | undefined;
|
|
19
19
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
20
20
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|