@laioutr/app-shopware 0.14.4
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 +21 -0
- package/README.md +146 -0
- package/dist/module.d.mts +88 -0
- package/dist/module.json +9 -0
- package/dist/module.mjs +60 -0
- package/dist/runtime/app/components/ShopwareEmbedFrame.d.vue.ts +14 -0
- package/dist/runtime/app/components/ShopwareEmbedFrame.vue +57 -0
- package/dist/runtime/app/components/ShopwareEmbedFrame.vue.d.ts +14 -0
- package/dist/runtime/app/composables/useShopwareEmbedBridge.d.ts +24 -0
- package/dist/runtime/app/composables/useShopwareEmbedBridge.js +22 -0
- package/dist/runtime/app/const/bridge.d.ts +63 -0
- package/dist/runtime/app/const/bridge.js +9 -0
- package/dist/runtime/app/image/providers/shopware.d.ts +7 -0
- package/dist/runtime/app/image/providers/shopware.js +44 -0
- package/dist/runtime/app/lib/createBridgeHandler.d.ts +44 -0
- package/dist/runtime/app/lib/createBridgeHandler.js +40 -0
- package/dist/runtime/app/lib/isTrustedOrigin.d.ts +9 -0
- package/dist/runtime/app/lib/isTrustedOrigin.js +8 -0
- package/dist/runtime/app/lib/parseBridgeMessage.d.ts +11 -0
- package/dist/runtime/app/lib/parseBridgeMessage.js +34 -0
- package/dist/runtime/app/plugins/pagetypes.d.ts +7 -0
- package/dist/runtime/app/plugins/pagetypes.js +6 -0
- package/dist/runtime/app/public/app-shopware/image-placeholder.svg +7 -0
- package/dist/runtime/app/public/app-shopware/shopware-logo.svg +10 -0
- package/dist/runtime/app/sections/SectionShopwareCheckout.d.vue.ts +0 -0
- package/dist/runtime/app/sections/SectionShopwareCheckout.vue +44 -0
- package/dist/runtime/app/sections/SectionShopwareCheckout.vue.d.ts +0 -0
- package/dist/runtime/index.d.ts +1 -0
- package/dist/runtime/index.js +1 -0
- package/dist/runtime/server/client/shopwareAdminClientFactory.d.ts +4 -0
- package/dist/runtime/server/client/shopwareAdminClientFactory.js +13 -0
- package/dist/runtime/server/client/shopwareClientFactory.d.ts +3 -0
- package/dist/runtime/server/client/shopwareClientFactory.js +18 -0
- package/dist/runtime/server/composable/useGetProductParentId.d.ts +4 -0
- package/dist/runtime/server/composable/useGetProductParentId.js +41 -0
- package/dist/runtime/server/const/checkout.d.ts +7 -0
- package/dist/runtime/server/const/checkout.js +2 -0
- package/dist/runtime/server/const/cookieKeys.d.ts +1 -0
- package/dist/runtime/server/const/cookieKeys.js +1 -0
- package/dist/runtime/server/const/fallbacks.d.ts +5 -0
- package/dist/runtime/server/const/fallbacks.js +14 -0
- package/dist/runtime/server/const/includes.d.ts +1 -0
- package/dist/runtime/server/const/includes.js +1 -0
- package/dist/runtime/server/const/passthroughTokens.d.ts +14 -0
- package/dist/runtime/server/const/passthroughTokens.js +10 -0
- package/dist/runtime/server/media-libraries/shopware.d.ts +20 -0
- package/dist/runtime/server/media-libraries/shopware.js +90 -0
- package/dist/runtime/server/middleware/defineShopware.d.ts +7 -0
- package/dist/runtime/server/middleware/defineShopware.js +33 -0
- package/dist/runtime/server/orchestr/auth/login.action.d.ts +2 -0
- package/dist/runtime/server/orchestr/auth/login.action.js +17 -0
- package/dist/runtime/server/orchestr/cart/add-item.action.d.ts +2 -0
- package/dist/runtime/server/orchestr/cart/add-item.action.js +46 -0
- package/dist/runtime/server/orchestr/cart/base.resolver.d.ts +2 -0
- package/dist/runtime/server/orchestr/cart/base.resolver.js +28 -0
- package/dist/runtime/server/orchestr/cart/cart-items.link.d.ts +2 -0
- package/dist/runtime/server/orchestr/cart/cart-items.link.js +10 -0
- package/dist/runtime/server/orchestr/cart/get-checkout-url.action.d.ts +10 -0
- package/dist/runtime/server/orchestr/cart/get-checkout-url.action.js +6 -0
- package/dist/runtime/server/orchestr/cart/get-current.query.d.ts +2 -0
- package/dist/runtime/server/orchestr/cart/get-current.query.js +9 -0
- package/dist/runtime/server/orchestr/cart/remove-items.action.d.ts +2 -0
- package/dist/runtime/server/orchestr/cart/remove-items.action.js +14 -0
- package/dist/runtime/server/orchestr/cart/update-items.action.d.ts +2 -0
- package/dist/runtime/server/orchestr/cart/update-items.action.js +16 -0
- package/dist/runtime/server/orchestr/cart-item/base.resolver.d.ts +2 -0
- package/dist/runtime/server/orchestr/cart-item/base.resolver.js +33 -0
- package/dist/runtime/server/orchestr/category/all.query.d.ts +2 -0
- package/dist/runtime/server/orchestr/category/all.query.js +15 -0
- package/dist/runtime/server/orchestr/category/base.resolver.d.ts +2 -0
- package/dist/runtime/server/orchestr/category/base.resolver.js +47 -0
- package/dist/runtime/server/orchestr/category/bySlug.query.d.ts +2 -0
- package/dist/runtime/server/orchestr/category/bySlug.query.js +12 -0
- package/dist/runtime/server/orchestr/category/child-categories.link.d.ts +2 -0
- package/dist/runtime/server/orchestr/category/child-categories.link.js +36 -0
- package/dist/runtime/server/orchestr/category/listing-page.page-index.d.ts +2 -0
- package/dist/runtime/server/orchestr/category/listing-page.page-index.js +79 -0
- package/dist/runtime/server/orchestr/category/menu-item.link.d.ts +2 -0
- package/dist/runtime/server/orchestr/category/menu-item.link.js +10 -0
- package/dist/runtime/server/orchestr/menu/base.resolver.d.ts +2 -0
- package/dist/runtime/server/orchestr/menu/base.resolver.js +63 -0
- package/dist/runtime/server/orchestr/menu/byAlias.query.d.ts +2 -0
- package/dist/runtime/server/orchestr/menu/byAlias.query.js +29 -0
- package/dist/runtime/server/orchestr/menu/byAlias.template.d.ts +2 -0
- package/dist/runtime/server/orchestr/menu/byAlias.template.js +25 -0
- package/dist/runtime/server/orchestr/newsletter/subscribe.action.d.ts +2 -0
- package/dist/runtime/server/orchestr/newsletter/subscribe.action.js +27 -0
- package/dist/runtime/server/orchestr/product/base.resolver.d.ts +2 -0
- package/dist/runtime/server/orchestr/product/base.resolver.js +119 -0
- package/dist/runtime/server/orchestr/product/breadcrumb.link.d.ts +2 -0
- package/dist/runtime/server/orchestr/product/breadcrumb.link.js +57 -0
- package/dist/runtime/server/orchestr/product/byCategoryId.query.d.ts +2 -0
- package/dist/runtime/server/orchestr/product/byCategoryId.query.js +47 -0
- package/dist/runtime/server/orchestr/product/byCategorySlug.query.d.ts +2 -0
- package/dist/runtime/server/orchestr/product/byCategorySlug.query.js +59 -0
- package/dist/runtime/server/orchestr/product/byCategorySlug.template.d.ts +2 -0
- package/dist/runtime/server/orchestr/product/byCategorySlug.template.js +40 -0
- package/dist/runtime/server/orchestr/product/bySlug.query.d.ts +2 -0
- package/dist/runtime/server/orchestr/product/bySlug.query.js +19 -0
- package/dist/runtime/server/orchestr/product/detail-page.page-index.d.ts +3 -0
- package/dist/runtime/server/orchestr/product/detail-page.page-index.js +58 -0
- package/dist/runtime/server/orchestr/product/reviews.link.d.ts +2 -0
- package/dist/runtime/server/orchestr/product/reviews.link.js +27 -0
- package/dist/runtime/server/orchestr/product/search.query.d.ts +2 -0
- package/dist/runtime/server/orchestr/product/search.query.js +43 -0
- package/dist/runtime/server/orchestr/product/variants.link.d.ts +2 -0
- package/dist/runtime/server/orchestr/product/variants.link.js +18 -0
- package/dist/runtime/server/orchestr/product-variant/base.resolver.d.ts +2 -0
- package/dist/runtime/server/orchestr/product-variant/base.resolver.js +129 -0
- package/dist/runtime/server/orchestr/review/base.resolver.d.ts +2 -0
- package/dist/runtime/server/orchestr/review/base.resolver.js +38 -0
- package/dist/runtime/server/orchestr/review/create-review.action.d.ts +2 -0
- package/dist/runtime/server/orchestr/review/create-review.action.js +14 -0
- package/dist/runtime/server/orchestr/suggested-search/suggested-search-entry.link.d.ts +2 -0
- package/dist/runtime/server/orchestr/suggested-search/suggested-search-entry.link.js +14 -0
- package/dist/runtime/server/orchestr/suggested-search/suggested-search.query.d.ts +2 -0
- package/dist/runtime/server/orchestr/suggested-search/suggested-search.query.js +54 -0
- package/dist/runtime/server/orchestr/suggested-search-entry/base.resolver.d.ts +2 -0
- package/dist/runtime/server/orchestr/suggested-search-entry/base.resolver.js +22 -0
- package/dist/runtime/server/orchestr-helper/isLinkSingle.d.ts +2 -0
- package/dist/runtime/server/orchestr-helper/isLinkSingle.js +1 -0
- package/dist/runtime/server/orchestr-helper/matchAndMap.d.ts +1 -0
- package/dist/runtime/server/orchestr-helper/matchAndMap.js +15 -0
- package/dist/runtime/server/orchestr-helper/requestedFields.d.ts +18 -0
- package/dist/runtime/server/orchestr-helper/requestedFields.js +145 -0
- package/dist/runtime/server/routes/adopt-session.post.d.ts +8 -0
- package/dist/runtime/server/routes/adopt-session.post.js +16 -0
- package/dist/runtime/server/routes/checkout.d.ts +16 -0
- package/dist/runtime/server/routes/checkout.js +31 -0
- package/dist/runtime/server/shopware-helper/adoptSession.d.ts +18 -0
- package/dist/runtime/server/shopware-helper/adoptSession.js +23 -0
- package/dist/runtime/server/shopware-helper/bootstrapContextToken.d.ts +18 -0
- package/dist/runtime/server/shopware-helper/bootstrapContextToken.js +22 -0
- package/dist/runtime/server/shopware-helper/cartErrors.d.ts +11 -0
- package/dist/runtime/server/shopware-helper/cartErrors.js +21 -0
- package/dist/runtime/server/shopware-helper/cartMapper.d.ts +14 -0
- package/dist/runtime/server/shopware-helper/cartMapper.js +83 -0
- package/dist/runtime/server/shopware-helper/categoryFlattener.d.ts +75 -0
- package/dist/runtime/server/shopware-helper/categoryFlattener.js +11 -0
- package/dist/runtime/server/shopware-helper/checkoutUrl.d.ts +12 -0
- package/dist/runtime/server/shopware-helper/checkoutUrl.js +5 -0
- package/dist/runtime/server/shopware-helper/contextTokenCookie.d.ts +11 -0
- package/dist/runtime/server/shopware-helper/contextTokenCookie.js +7 -0
- package/dist/runtime/server/shopware-helper/facetMapper.d.ts +38 -0
- package/dist/runtime/server/shopware-helper/facetMapper.js +79 -0
- package/dist/runtime/server/shopware-helper/fetchAllProductVariants.d.ts +137 -0
- package/dist/runtime/server/shopware-helper/fetchAllProductVariants.js +16 -0
- package/dist/runtime/server/shopware-helper/getCart.d.ts +10 -0
- package/dist/runtime/server/shopware-helper/getCart.js +4 -0
- package/dist/runtime/server/shopware-helper/isShopwareId.d.ts +4 -0
- package/dist/runtime/server/shopware-helper/isShopwareId.js +2 -0
- package/dist/runtime/server/shopware-helper/mappers/slugMapper.d.ts +10 -0
- package/dist/runtime/server/shopware-helper/mappers/slugMapper.js +9 -0
- package/dist/runtime/server/shopware-helper/mediaMapper.d.ts +8 -0
- package/dist/runtime/server/shopware-helper/mediaMapper.js +42 -0
- package/dist/runtime/server/shopware-helper/pageIndexEntries.d.ts +20 -0
- package/dist/runtime/server/shopware-helper/pageIndexEntries.js +22 -0
- package/dist/runtime/server/shopware-helper/persistContextToken.d.ts +13 -0
- package/dist/runtime/server/shopware-helper/persistContextToken.js +12 -0
- package/dist/runtime/server/shopware-helper/productLocate.d.ts +14 -0
- package/dist/runtime/server/shopware-helper/productLocate.js +9 -0
- package/dist/runtime/server/shopware-helper/readCategoryPageMeta.d.ts +10 -0
- package/dist/runtime/server/shopware-helper/readCategoryPageMeta.js +19 -0
- package/dist/runtime/server/shopware-helper/readProductPageMeta.d.ts +10 -0
- package/dist/runtime/server/shopware-helper/readProductPageMeta.js +20 -0
- package/dist/runtime/server/shopware-helper/resolveAdoptSession.d.ts +23 -0
- package/dist/runtime/server/shopware-helper/resolveAdoptSession.js +13 -0
- package/dist/runtime/server/shopware-helper/resolveCheckout.d.ts +39 -0
- package/dist/runtime/server/shopware-helper/resolveCheckout.js +24 -0
- package/dist/runtime/server/shopware-helper/sessionHandoff.d.ts +27 -0
- package/dist/runtime/server/shopware-helper/sessionHandoff.js +29 -0
- package/dist/runtime/server/shopware-helper/sortingMapper.d.ts +3 -0
- package/dist/runtime/server/shopware-helper/sortingMapper.js +7 -0
- package/dist/runtime/server/shopware-helper/storeApiPageFetcher.d.ts +20 -0
- package/dist/runtime/server/shopware-helper/storeApiPageFetcher.js +10 -0
- package/dist/runtime/server/shopware-helper/swTranslated.d.ts +7 -0
- package/dist/runtime/server/shopware-helper/swTranslated.js +4 -0
- package/dist/runtime/server/shopware-helper/system/getCurrentSystemEntities.d.ts +20 -0
- package/dist/runtime/server/shopware-helper/system/getCurrentSystemEntities.js +39 -0
- package/dist/runtime/server/shopware-helper/system/getSystemEntities.d.ts +31 -0
- package/dist/runtime/server/shopware-helper/system/getSystemEntities.js +47 -0
- package/dist/runtime/server/shopware-helper/useSeoResolver.d.ts +11 -0
- package/dist/runtime/server/shopware-helper/useSeoResolver.js +70 -0
- package/dist/runtime/server/tsconfig.json +3 -0
- package/dist/runtime/server/types/shopware.d.ts +29 -0
- package/dist/runtime/server/types/shopware.js +0 -0
- package/dist/runtime/server/types/storeApiTypes.d.ts +5677 -0
- package/dist/runtime/shared/const/checkout.d.ts +18 -0
- package/dist/runtime/shared/const/checkout.js +2 -0
- package/dist/runtime/shared/pageTypes/checkout.pagetype.d.ts +21 -0
- package/dist/runtime/shared/pageTypes/checkout.pagetype.js +12 -0
- package/dist/types.d.mts +3 -0
- package/package.json +96 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) Laioutr GmbH
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# Shopware
|
|
2
|
+
|
|
3
|
+
[![Laioutr][laioutr-src]][laioutr-href]
|
|
4
|
+
[![npm version][npm-version-src]][npm-version-href]
|
|
5
|
+
[![npm downloads][npm-downloads-src]][npm-downloads-href]
|
|
6
|
+
[![License][license-src]][license-href]
|
|
7
|
+
[![Nuxt][nuxt-src]][nuxt-href]
|
|
8
|
+
|
|
9
|
+
[Laioutr](https://laioutr.com) App integrating [Shopware 6](https://www.shopware.com) as the
|
|
10
|
+
commerce backend for a Laioutr storefront, using Nuxt.
|
|
11
|
+
|
|
12
|
+
See [laioutr.com](https://laioutr.com) for more information about Laioutr.
|
|
13
|
+
|
|
14
|
+
- [✨ Release Notes](/CHANGELOG.md)
|
|
15
|
+
|
|
16
|
+
## Features
|
|
17
|
+
|
|
18
|
+
- Products, categories, menus, reviews and search served through Laioutr's Orchestr layer, mapped
|
|
19
|
+
onto the canonical entity model — so storefront components stay backend-agnostic
|
|
20
|
+
- Cart as a first-class entity: add, update and remove items, with server-held Shopware context
|
|
21
|
+
tokens that never reach the browser
|
|
22
|
+
- Embedded checkout — the Shopware storefront's own checkout rendered in-page, with a same-origin
|
|
23
|
+
session handoff so the visitor is never bounced to another domain
|
|
24
|
+
- Auth bridge: a storefront login or logout inside the embedded checkout propagates back into the
|
|
25
|
+
Laioutr session, and hooks let a host app mirror it into an external IdP
|
|
26
|
+
- Page indexes for product detail and category listing pages, so Studio can resolve a URL to the
|
|
27
|
+
entity behind it
|
|
28
|
+
- Shopware media as a Laioutr media library, plus a `@nuxt/image` provider that serves Shopware's
|
|
29
|
+
own thumbnails
|
|
30
|
+
- Newsletter subscription and review submission as Orchestr actions
|
|
31
|
+
|
|
32
|
+
## Requirements
|
|
33
|
+
|
|
34
|
+
- A Shopware 6 instance with the Store API enabled
|
|
35
|
+
- An [Admin API integration](https://developer.shopware.com/docs/guides/integrations-api/) — used
|
|
36
|
+
for the media library and for reading the sales channel's currency and language
|
|
37
|
+
- [`LaioutrConnector`](https://github.com/laioutr/shopware-laioutr-connector), the companion
|
|
38
|
+
Shopware plugin. Required for checkout: it exposes the `connect-session` route that redeems the
|
|
39
|
+
single-use handoff code and adopts the cart into a storefront session. Without it, product,
|
|
40
|
+
category, menu and search still work; `checkoutLink` and `GetCheckoutUrlAction` do not.
|
|
41
|
+
|
|
42
|
+
## Configuration
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
// nuxt.config.ts
|
|
46
|
+
export default defineNuxtConfig({
|
|
47
|
+
modules: ['@laioutr/app-shopware'],
|
|
48
|
+
'@laioutr/app-shopware': {
|
|
49
|
+
endpoint: 'https://shop.example.com/store-api',
|
|
50
|
+
accessToken: 'SWSC...',
|
|
51
|
+
adminEndpoint: 'https://shop.example.com/api',
|
|
52
|
+
adminClientId: 'SWIA...',
|
|
53
|
+
adminClientSecret: '...',
|
|
54
|
+
storefrontUrl: 'https://shop.example.com',
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
| Option | Required | Purpose |
|
|
60
|
+
| --- | --- | --- |
|
|
61
|
+
| `endpoint` | yes | Store API base URL, `https://<host>/store-api`. |
|
|
62
|
+
| `accessToken` | yes | Sales channel access token. |
|
|
63
|
+
| `adminEndpoint` | yes | Admin API base URL, `https://<host>/api`. |
|
|
64
|
+
| `adminClientId` | yes | Admin API integration ID. |
|
|
65
|
+
| `adminClientSecret` | yes | Admin API integration secret. |
|
|
66
|
+
| `storefrontUrl` | for checkout | Storefront base URL where `LaioutrConnector` is installed. Its origin is exposed publicly so the embedded checkout can pin `postMessage` traffic. |
|
|
67
|
+
| `checkoutLoginCallbackUrl` | no | Absolute URL the storefront returns to after a login inside checkout. Defaults to the request origin; point it at the project's IdP login route for external-IdP projects. |
|
|
68
|
+
| `checkoutLogoutCallbackUrl` | no | Absolute URL the storefront returns to after a logout inside checkout. Point it at the IdP's RP-logout route so a storefront logout ends the external session too. |
|
|
69
|
+
|
|
70
|
+
`accessToken`, `adminClientId` and `adminClientSecret` are secrets and stay server-side: they land
|
|
71
|
+
in the private runtime config, and only `storefrontUrl`'s origin is exposed publicly. Read them
|
|
72
|
+
from the environment rather than committing them to `nuxt.config.ts`.
|
|
73
|
+
|
|
74
|
+
## Checkout
|
|
75
|
+
|
|
76
|
+
The app registers a `Checkout` page type. Tag one page with it in Studio and drop the
|
|
77
|
+
**Shopware Checkout** section on it; the cart's `checkoutLink` resolves to that page, so nothing
|
|
78
|
+
in the storefront needs to know its slug.
|
|
79
|
+
|
|
80
|
+
Two same-origin routes carry the handoff, both under the app's `/app-shopware/` namespace:
|
|
81
|
+
|
|
82
|
+
| Route | Purpose |
|
|
83
|
+
| --- | --- |
|
|
84
|
+
| `GET /app-shopware/checkout` | Mints a single-use handoff code server-side and redirects to the storefront's `connect-session`, which adopts the cart and lands on the confirm page. |
|
|
85
|
+
| `POST /app-shopware/adopt-session` | Called by the checkout section when the embedded storefront reports a login or logout, so the new session is adopted (or cleared) server-side. |
|
|
86
|
+
|
|
87
|
+
The Shopware context token is held server-side and passed by code, never by URL and never in the
|
|
88
|
+
browser — a code is redeemed once and cannot be replayed.
|
|
89
|
+
|
|
90
|
+
### Session hooks
|
|
91
|
+
|
|
92
|
+
A host app that owns its own session store can take over token custody through two Nitro hooks:
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
// server/plugins/shopware-session.ts
|
|
96
|
+
export default defineNitroPlugin((nitro) => {
|
|
97
|
+
// Bail hook: supply the context token from your own store. The cookie is the fallback.
|
|
98
|
+
// Read-only — it must not mint a token.
|
|
99
|
+
nitro.hooks.hook('shopware:context-token:resolve', ({ event, result }) => {
|
|
100
|
+
result.token = mySessionStore.get(event);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// Fired after the token is persisted (`token` is the new value) or cleared on a
|
|
104
|
+
// storefront logout (`token` is `null`).
|
|
105
|
+
nitro.hooks.hook('shopware:context-token:changed', ({ event, token }) => {
|
|
106
|
+
token ? mySessionStore.set(event, token) : mySessionStore.clear(event);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Development
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
pnpm install
|
|
115
|
+
cp .env.example .env # point it at a Shopware instance
|
|
116
|
+
pnpm dev # playground on http://localhost:3000
|
|
117
|
+
pnpm test
|
|
118
|
+
pnpm lint
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
`@laioutr-core/*` and `@laioutr-app/ui` resolve from Laioutr's registry. Before installing, render
|
|
122
|
+
the template with your token:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
sed "s|NPM_LAIOUTR_TOKEN|$YOUR_TOKEN|" .npmrc.config > .npmrc
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Releases go through [changesets](https://github.com/changesets/changesets): run `pnpm changeset` to
|
|
129
|
+
describe your change, and merging the generated "Version Packages" PR publishes to npm.
|
|
130
|
+
|
|
131
|
+
## License
|
|
132
|
+
|
|
133
|
+
[MIT](./LICENSE.md) — Laioutr GmbH
|
|
134
|
+
|
|
135
|
+
<!-- Badges -->
|
|
136
|
+
|
|
137
|
+
[laioutr-src]: https://img.shields.io/badge/Laioutr-App-000?style=flat&labelColor=020420
|
|
138
|
+
[laioutr-href]: https://laioutr.com
|
|
139
|
+
[npm-version-src]: https://img.shields.io/npm/v/@laioutr/app-shopware/latest.svg?style=flat&colorA=020420&colorB=00DC82
|
|
140
|
+
[npm-version-href]: https://npmjs.com/package/@laioutr/app-shopware
|
|
141
|
+
[npm-downloads-src]: https://img.shields.io/npm/dm/@laioutr/app-shopware.svg?style=flat&colorA=020420&colorB=00DC82
|
|
142
|
+
[npm-downloads-href]: https://npmjs.com/package/@laioutr/app-shopware
|
|
143
|
+
[license-src]: https://img.shields.io/npm/l/@laioutr/app-shopware.svg?style=flat&colorA=020420&colorB=00DC82
|
|
144
|
+
[license-href]: https://npmjs.com/package/@laioutr/app-shopware
|
|
145
|
+
[nuxt-src]: https://img.shields.io/badge/Nuxt-020420?logo=nuxt.js
|
|
146
|
+
[nuxt-href]: https://nuxt.com
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { HookResult, NuxtModule } from '@nuxt/schema';
|
|
2
|
+
import { H3Event } from 'h3';
|
|
3
|
+
|
|
4
|
+
declare module 'vue' {
|
|
5
|
+
interface GlobalComponents {
|
|
6
|
+
}
|
|
7
|
+
interface ComponentCustomProperties {
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
declare module '@nuxt/schema' {
|
|
11
|
+
interface PublicRuntimeConfig {
|
|
12
|
+
['@laioutr/app-shopware']: RuntimeConfigModulePublic;
|
|
13
|
+
}
|
|
14
|
+
interface RuntimeConfig {
|
|
15
|
+
['@laioutr/app-shopware']: RuntimeConfigModulePrivate;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
declare module 'nitropack' {
|
|
19
|
+
interface NitroRuntimeHooks {
|
|
20
|
+
/**
|
|
21
|
+
* Fetch (bail): a host handler may set `result.token` to supply the Shopware context token
|
|
22
|
+
* from its own session store; the cookie is the fallback. Read-only; must NOT mint a token.
|
|
23
|
+
*/
|
|
24
|
+
'shopware:context-token:resolve': (args: {
|
|
25
|
+
event: H3Event;
|
|
26
|
+
result: {
|
|
27
|
+
token?: string;
|
|
28
|
+
};
|
|
29
|
+
}) => HookResult;
|
|
30
|
+
/**
|
|
31
|
+
* Notification: fired after the context token is (re)persisted (`token` is the new value) or
|
|
32
|
+
* cleared on a storefront logout (`token` is `null`), so a host can mirror the change into its
|
|
33
|
+
* own store (e.g. keyed by the external IdP subject) — persisting on a value, clearing on null.
|
|
34
|
+
*/
|
|
35
|
+
'shopware:context-token:changed': (args: {
|
|
36
|
+
event: H3Event;
|
|
37
|
+
token: string | null;
|
|
38
|
+
}) => HookResult;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The options the module adds to the nuxt.config.ts.
|
|
44
|
+
*/
|
|
45
|
+
interface ModuleOptions {
|
|
46
|
+
endpoint: string;
|
|
47
|
+
accessToken: string;
|
|
48
|
+
adminEndpoint: string;
|
|
49
|
+
adminClientId: string;
|
|
50
|
+
adminClientSecret: string;
|
|
51
|
+
/**
|
|
52
|
+
* Base URL of the Shopware storefront where the LaioutrConnector plugin is installed
|
|
53
|
+
* (e.g. `https://shop.example.com`). Required for `GetCheckoutUrlAction` / the cart
|
|
54
|
+
* `checkoutLink`; those are unavailable when it is unset.
|
|
55
|
+
*/
|
|
56
|
+
storefrontUrl?: string;
|
|
57
|
+
/**
|
|
58
|
+
* Absolute URL the storefront returns to after a login inside checkout. Defaults to the
|
|
59
|
+
* request origin. Set to the project's IdP login route for external-IdP (SSO) projects.
|
|
60
|
+
*/
|
|
61
|
+
checkoutLoginCallbackUrl?: string;
|
|
62
|
+
/**
|
|
63
|
+
* Absolute URL the storefront returns to after a logout inside checkout. Defaults to the
|
|
64
|
+
* request origin. Set to the project's IdP RP-logout route so a storefront logout ends the
|
|
65
|
+
* external session too.
|
|
66
|
+
*/
|
|
67
|
+
checkoutLogoutCallbackUrl?: string;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The config the module adds to nuxt.runtimeConfig.public['@laioutr/app-shopware']
|
|
71
|
+
*/
|
|
72
|
+
interface RuntimeConfigModulePublic {
|
|
73
|
+
/**
|
|
74
|
+
* Origin (scheme + host) of the Shopware storefront, derived from {@link
|
|
75
|
+
* ModuleOptions.storefrontUrl}. Exposed publicly so the embedded checkout section can
|
|
76
|
+
* validate and pin `postMessage` traffic to the storefront frame. Empty when unset.
|
|
77
|
+
*/
|
|
78
|
+
storefrontOrigin: string;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* The config the module adds to nuxt.runtimeConfig['@laioutr/app-shopware']
|
|
82
|
+
*/
|
|
83
|
+
interface RuntimeConfigModulePrivate extends ModuleOptions {
|
|
84
|
+
}
|
|
85
|
+
declare const module$1: NuxtModule<ModuleOptions>;
|
|
86
|
+
|
|
87
|
+
export { module$1 as default };
|
|
88
|
+
export type { ModuleOptions, RuntimeConfigModulePrivate, RuntimeConfigModulePublic };
|
package/dist/module.json
ADDED
package/dist/module.mjs
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { defineNuxtModule, createResolver, addServerHandler, addPlugin, installModule } from '@nuxt/kit';
|
|
2
|
+
import { defu } from 'defu';
|
|
3
|
+
import { CHECKOUT_ENDPOINT_PATH, ADOPT_SESSION_ENDPOINT_PATH } from '../dist/runtime/server/const/checkout.js';
|
|
4
|
+
import { registerLaioutrApp } from '@laioutr-core/kit';
|
|
5
|
+
|
|
6
|
+
const name = "@laioutr/app-shopware";
|
|
7
|
+
const version = "0.14.4";
|
|
8
|
+
|
|
9
|
+
const module$1 = defineNuxtModule({
|
|
10
|
+
meta: {
|
|
11
|
+
name,
|
|
12
|
+
version,
|
|
13
|
+
configKey: name
|
|
14
|
+
},
|
|
15
|
+
async setup(options, nuxt) {
|
|
16
|
+
const { resolve } = createResolver(import.meta.url);
|
|
17
|
+
const resolveRuntimeModule = (path) => resolve("./runtime", path);
|
|
18
|
+
nuxt.options.build.transpile.push(resolve("./runtime"));
|
|
19
|
+
nuxt.options.runtimeConfig[name] = defu(nuxt.options.runtimeConfig[name], options);
|
|
20
|
+
nuxt.options.runtimeConfig.public[name] = defu(nuxt.options.runtimeConfig.public[name], {
|
|
21
|
+
storefrontOrigin: options.storefrontUrl ? new URL(options.storefrontUrl).origin : ""
|
|
22
|
+
});
|
|
23
|
+
nuxt.options.nitro.publicAssets ??= [];
|
|
24
|
+
nuxt.options.nitro.publicAssets.push({
|
|
25
|
+
dir: resolveRuntimeModule("./app/public"),
|
|
26
|
+
maxAge: 60 * 60 * 24 * 7
|
|
27
|
+
// 7 days
|
|
28
|
+
});
|
|
29
|
+
addServerHandler({
|
|
30
|
+
route: CHECKOUT_ENDPOINT_PATH,
|
|
31
|
+
method: "get",
|
|
32
|
+
handler: resolveRuntimeModule("./server/routes/checkout")
|
|
33
|
+
});
|
|
34
|
+
addServerHandler({
|
|
35
|
+
route: ADOPT_SESSION_ENDPOINT_PATH,
|
|
36
|
+
method: "post",
|
|
37
|
+
handler: resolveRuntimeModule("./server/routes/adopt-session.post")
|
|
38
|
+
});
|
|
39
|
+
await registerLaioutrApp({
|
|
40
|
+
name,
|
|
41
|
+
version,
|
|
42
|
+
orchestrDirs: [resolveRuntimeModule("server/orchestr")],
|
|
43
|
+
sections: [resolveRuntimeModule("app/sections/")],
|
|
44
|
+
nuxtImageProviders: {
|
|
45
|
+
shopware: {
|
|
46
|
+
name: "shopware",
|
|
47
|
+
provider: resolveRuntimeModule("./app/image/providers/shopware")
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
mediaLibraryProviders: [resolveRuntimeModule("./server/media-libraries/shopware")]
|
|
51
|
+
});
|
|
52
|
+
addPlugin(resolveRuntimeModule("./app/plugins/pagetypes"));
|
|
53
|
+
if (nuxt.options._prepare) {
|
|
54
|
+
await installModule("@laioutr-core/frontend-core");
|
|
55
|
+
await installModule("@laioutr-app/ui");
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
export { module$1 as default };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
declare const _default: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
2
|
+
"checkout-finish": (orderId: string) => any;
|
|
3
|
+
"auth-changed": (payload: {
|
|
4
|
+
from: string;
|
|
5
|
+
code?: string;
|
|
6
|
+
}) => any;
|
|
7
|
+
}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{
|
|
8
|
+
"onCheckout-finish"?: ((orderId: string) => any) | undefined;
|
|
9
|
+
"onAuth-changed"?: ((payload: {
|
|
10
|
+
from: string;
|
|
11
|
+
code?: string;
|
|
12
|
+
}) => any) | undefined;
|
|
13
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
14
|
+
export default _default;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed, ref, useRuntimeConfig } from "#imports";
|
|
3
|
+
import { CHECKOUT_ENDPOINT_PATH } from "../../shared/const/checkout";
|
|
4
|
+
import { useShopwareEmbedBridge } from "../composables/useShopwareEmbedBridge";
|
|
5
|
+
const emit = defineEmits(["checkout-finish", "auth-changed"]);
|
|
6
|
+
const storefrontOrigin = computed(() => useRuntimeConfig().public["@laioutr/app-shopware"]?.storefrontOrigin ?? "");
|
|
7
|
+
const frameRef = ref(null);
|
|
8
|
+
const height = ref();
|
|
9
|
+
const loaded = ref(false);
|
|
10
|
+
let hasShownFramePage = false;
|
|
11
|
+
const onFramePageLoaded = () => {
|
|
12
|
+
loaded.value = true;
|
|
13
|
+
if (hasShownFramePage) {
|
|
14
|
+
window.scrollTo({ top: 0, left: 0 });
|
|
15
|
+
}
|
|
16
|
+
hasShownFramePage = true;
|
|
17
|
+
};
|
|
18
|
+
const { sendInit } = useShopwareEmbedBridge(frameRef, {
|
|
19
|
+
storefrontOrigin: storefrontOrigin.value,
|
|
20
|
+
onResize: (value) => height.value = value,
|
|
21
|
+
onPageLoaded: onFramePageLoaded,
|
|
22
|
+
onCheckoutFinish: (orderId) => emit("checkout-finish", orderId),
|
|
23
|
+
onAuthChanged: (payload) => emit("auth-changed", payload)
|
|
24
|
+
// laioutr:pw-recovery is received but unused in v1.
|
|
25
|
+
});
|
|
26
|
+
const onFrameLoad = () => {
|
|
27
|
+
loaded.value = true;
|
|
28
|
+
sendInit();
|
|
29
|
+
};
|
|
30
|
+
const frameStyle = computed(() => ({ height: height.value ? `${height.value}px` : "600px" }));
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<template>
|
|
34
|
+
<div class="shopware-embed-frame">
|
|
35
|
+
<div v-if="!storefrontOrigin" class="shopware-embed-frame__notice">
|
|
36
|
+
The Shopware storefront URL is not configured, so the checkout cannot be embedded.
|
|
37
|
+
</div>
|
|
38
|
+
<template v-else>
|
|
39
|
+
<div v-if="!loaded" class="shopware-embed-frame__loading" role="status">
|
|
40
|
+
<LLoadingSpinner variant="row" />
|
|
41
|
+
<span class="shopware-embed-frame__loading-label">Loading checkout…</span>
|
|
42
|
+
</div>
|
|
43
|
+
<iframe
|
|
44
|
+
ref="frameRef"
|
|
45
|
+
:src="CHECKOUT_ENDPOINT_PATH"
|
|
46
|
+
title="Checkout"
|
|
47
|
+
class="shopware-embed-frame__iframe"
|
|
48
|
+
:style="frameStyle"
|
|
49
|
+
@load="onFrameLoad"
|
|
50
|
+
/>
|
|
51
|
+
</template>
|
|
52
|
+
</div>
|
|
53
|
+
</template>
|
|
54
|
+
|
|
55
|
+
<style scoped>
|
|
56
|
+
.shopware-embed-frame{position:relative}.shopware-embed-frame__iframe{border:0;display:block;width:100%}.shopware-embed-frame__notice{padding:1rem;text-align:center}.shopware-embed-frame__loading{align-items:center;background:var(--background-default);display:flex;flex-direction:column;gap:.75rem;inset:0;justify-content:center;padding:1rem;position:absolute;z-index:1}.shopware-embed-frame__loading-label{font-size:.875rem;opacity:.7}
|
|
57
|
+
</style>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
declare const _default: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
2
|
+
"checkout-finish": (orderId: string) => any;
|
|
3
|
+
"auth-changed": (payload: {
|
|
4
|
+
from: string;
|
|
5
|
+
code?: string;
|
|
6
|
+
}) => any;
|
|
7
|
+
}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{
|
|
8
|
+
"onCheckout-finish"?: ((orderId: string) => any) | undefined;
|
|
9
|
+
"onAuth-changed"?: ((payload: {
|
|
10
|
+
from: string;
|
|
11
|
+
code?: string;
|
|
12
|
+
}) => any) | undefined;
|
|
13
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
14
|
+
export default _default;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type Ref } from 'vue';
|
|
2
|
+
import { type AuthChangedPayload, type BridgePageLoadedPayload } from '../const/bridge.js';
|
|
3
|
+
export type UseShopwareEmbedBridgeOptions = {
|
|
4
|
+
/** Configured storefront origin; inbound messages are validated (pinned) against it. */
|
|
5
|
+
storefrontOrigin: string;
|
|
6
|
+
onResize?: (height: number) => void;
|
|
7
|
+
onPageLoaded?: (payload: BridgePageLoadedPayload) => void;
|
|
8
|
+
onCheckoutFinish?: (orderId: string) => void;
|
|
9
|
+
onPwRecovery?: () => void;
|
|
10
|
+
onAuthChanged?: (payload: AuthChangedPayload) => void;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Wires the parent side of the storefront `postMessage` bridge to the real `window`
|
|
14
|
+
* `message` event for the lifetime of the calling component. All decision logic lives in
|
|
15
|
+
* {@link createBridgeHandler} (unit-tested); this only adds/removes the listener and posts
|
|
16
|
+
* through the frame's `contentWindow`.
|
|
17
|
+
*
|
|
18
|
+
* Returns `sendInit` — call it on the iframe's `load` event to proactively complete the
|
|
19
|
+
* handshake, since the plugin's one-shot `ready` can fire before this listener attaches
|
|
20
|
+
* (the SSR iframe loads before Vue hydrates).
|
|
21
|
+
*/
|
|
22
|
+
export declare const useShopwareEmbedBridge: (frameRef: Ref<HTMLIFrameElement | null>, options: UseShopwareEmbedBridgeOptions) => {
|
|
23
|
+
sendInit: () => void;
|
|
24
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { onBeforeUnmount, onMounted } from "vue";
|
|
2
|
+
import { buildInitMessage } from "../const/bridge.js";
|
|
3
|
+
import { createBridgeHandler } from "../lib/createBridgeHandler.js";
|
|
4
|
+
export const useShopwareEmbedBridge = (frameRef, options) => {
|
|
5
|
+
const { handleMessage, sendInit } = createBridgeHandler({
|
|
6
|
+
getFrameWindow: () => frameRef.value?.contentWindow ?? null,
|
|
7
|
+
storefrontOrigin: options.storefrontOrigin,
|
|
8
|
+
postInit: (frameWindow, targetOrigin) => frameWindow.postMessage(buildInitMessage(), targetOrigin),
|
|
9
|
+
onResize: options.onResize,
|
|
10
|
+
onPageLoaded: options.onPageLoaded,
|
|
11
|
+
onCheckoutFinish: options.onCheckoutFinish,
|
|
12
|
+
onPwRecovery: options.onPwRecovery,
|
|
13
|
+
onAuthChanged: options.onAuthChanged
|
|
14
|
+
});
|
|
15
|
+
const listener = (event) => handleMessage(event);
|
|
16
|
+
onMounted(() => {
|
|
17
|
+
window.addEventListener("message", listener);
|
|
18
|
+
sendInit();
|
|
19
|
+
});
|
|
20
|
+
onBeforeUnmount(() => window.removeEventListener("message", listener));
|
|
21
|
+
return { sendInit };
|
|
22
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared postMessage contract with the LaioutrConnector storefront bridge
|
|
3
|
+
* (`Resources/public/laioutr-embed.js` in the `shopware-laioutr-integration` plugin).
|
|
4
|
+
*
|
|
5
|
+
* Every message uses the envelope `{ source, version, type, payload }`. The `source`
|
|
6
|
+
* discriminator lets each side ignore unrelated postMessage traffic; `version` guards
|
|
7
|
+
* against contract drift. Keep these constants in lock-step with the plugin's bridge.
|
|
8
|
+
*/
|
|
9
|
+
export declare const BRIDGE_SOURCE = "laioutr-shopware";
|
|
10
|
+
export declare const BRIDGE_VERSION = 1;
|
|
11
|
+
/** Handshake reply this (parent) frame posts to pin the storefront origin. */
|
|
12
|
+
export declare const BRIDGE_INIT_TYPE = "laioutr:init";
|
|
13
|
+
/** Messages the storefront bridge sends to this (parent) frame. */
|
|
14
|
+
export type BridgeInboundMessage = {
|
|
15
|
+
type: 'laioutr:ready';
|
|
16
|
+
payload: Record<string, never>;
|
|
17
|
+
} | {
|
|
18
|
+
type: 'laioutr:resize';
|
|
19
|
+
payload: {
|
|
20
|
+
height: number;
|
|
21
|
+
};
|
|
22
|
+
} | {
|
|
23
|
+
type: 'laioutr:page-loaded';
|
|
24
|
+
payload: {
|
|
25
|
+
path: string;
|
|
26
|
+
route: string | null;
|
|
27
|
+
navigationId: string | null;
|
|
28
|
+
salesChannelId: string | null;
|
|
29
|
+
};
|
|
30
|
+
} | {
|
|
31
|
+
type: 'laioutr:checkout-finish';
|
|
32
|
+
payload: {
|
|
33
|
+
orderId: string;
|
|
34
|
+
};
|
|
35
|
+
} | {
|
|
36
|
+
type: 'laioutr:pw-recovery';
|
|
37
|
+
payload: Record<string, never>;
|
|
38
|
+
} | {
|
|
39
|
+
type: 'laioutr:auth-changed';
|
|
40
|
+
payload: {
|
|
41
|
+
from: string;
|
|
42
|
+
code?: string;
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
export type BridgeInboundType = BridgeInboundMessage['type'];
|
|
46
|
+
export type BridgePageLoadedPayload = Extract<BridgeInboundMessage, {
|
|
47
|
+
type: 'laioutr:page-loaded';
|
|
48
|
+
}>['payload'];
|
|
49
|
+
export type AuthChangedPayload = Extract<BridgeInboundMessage, {
|
|
50
|
+
type: 'laioutr:auth-changed';
|
|
51
|
+
}>['payload'];
|
|
52
|
+
/** The full envelope as it arrives on `MessageEvent.data`. */
|
|
53
|
+
export type BridgeEnvelope = {
|
|
54
|
+
source: typeof BRIDGE_SOURCE;
|
|
55
|
+
version: typeof BRIDGE_VERSION;
|
|
56
|
+
} & BridgeInboundMessage;
|
|
57
|
+
/** The contentless handshake reply, posted to the pinned storefront origin. */
|
|
58
|
+
export declare const buildInitMessage: () => {
|
|
59
|
+
source: string;
|
|
60
|
+
version: number;
|
|
61
|
+
type: string;
|
|
62
|
+
payload: {};
|
|
63
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export const BRIDGE_SOURCE = "laioutr-shopware";
|
|
2
|
+
export const BRIDGE_VERSION = 1;
|
|
3
|
+
export const BRIDGE_INIT_TYPE = "laioutr:init";
|
|
4
|
+
export const buildInitMessage = () => ({
|
|
5
|
+
source: BRIDGE_SOURCE,
|
|
6
|
+
version: BRIDGE_VERSION,
|
|
7
|
+
type: BRIDGE_INIT_TYPE,
|
|
8
|
+
payload: {}
|
|
9
|
+
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ProviderGetImage } from '@nuxt/image';
|
|
2
|
+
/**
|
|
3
|
+
* Nuxt image provider using shopware thumbnails.
|
|
4
|
+
*
|
|
5
|
+
* The only modifications supported are "width" and "height". The provider tries to find a best-fit image from the available shopware thumbnails.
|
|
6
|
+
*/
|
|
7
|
+
export declare const getImage: ProviderGetImage;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const THUMBNAIL_MATCH_TOLERANCE = 2;
|
|
2
|
+
const matchThumbnail = (thumbnails, options) => {
|
|
3
|
+
if (!thumbnails.length) {
|
|
4
|
+
return void 0;
|
|
5
|
+
}
|
|
6
|
+
const { width, height } = options;
|
|
7
|
+
if (!width && !height) {
|
|
8
|
+
return thumbnails.at(-1);
|
|
9
|
+
}
|
|
10
|
+
const matchingThumbnail = thumbnails.find((thumbnail) => {
|
|
11
|
+
const thumbnailWidth = Math.max(thumbnail.width - THUMBNAIL_MATCH_TOLERANCE, 0);
|
|
12
|
+
const thumbnailHeight = Math.max(thumbnail.height - THUMBNAIL_MATCH_TOLERANCE, 0);
|
|
13
|
+
if (width && !height && thumbnailWidth >= width) {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
if (!width && height && thumbnailHeight >= height) {
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
if (width && height && thumbnailWidth >= width && thumbnailHeight >= height) {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
return false;
|
|
23
|
+
});
|
|
24
|
+
return matchingThumbnail ?? thumbnails.at(-1);
|
|
25
|
+
};
|
|
26
|
+
export const getImage = (src, options = {}) => {
|
|
27
|
+
const [orgSrc, rawThumbnailSrc] = src.split("#");
|
|
28
|
+
const thumbnailSrc = decodeURIComponent(rawThumbnailSrc ?? "");
|
|
29
|
+
const thumbnails = thumbnailSrc ? thumbnailSrc.split(", ").map((thumbnail) => {
|
|
30
|
+
const [url, size] = thumbnail.split(" ");
|
|
31
|
+
const [width, height] = size?.split("x").map(Number.parseInt).filter((num) => !Number.isNaN(num)) ?? [0, 0];
|
|
32
|
+
return {
|
|
33
|
+
url,
|
|
34
|
+
width: width ?? height ?? 0,
|
|
35
|
+
height: height ?? width ?? 0
|
|
36
|
+
};
|
|
37
|
+
}).sort((a, b) => a.width - b.width) : [];
|
|
38
|
+
const match = matchThumbnail(thumbnails, options.modifiers ?? {});
|
|
39
|
+
return {
|
|
40
|
+
url: match?.url ?? orgSrc,
|
|
41
|
+
width: match?.width ?? 0,
|
|
42
|
+
height: match?.height ?? 0
|
|
43
|
+
};
|
|
44
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type AuthChangedPayload, type BridgePageLoadedPayload } from '../const/bridge.js';
|
|
2
|
+
/** Minimal `MessageEvent` shape the handler reads — kept structural so it's testable without a DOM. */
|
|
3
|
+
export type BridgeMessageEvent = {
|
|
4
|
+
source: unknown;
|
|
5
|
+
origin: string;
|
|
6
|
+
data: unknown;
|
|
7
|
+
};
|
|
8
|
+
export type CreateBridgeHandlerOptions = {
|
|
9
|
+
/** The embedded frame's content window, or `null` before it mounts. */
|
|
10
|
+
getFrameWindow: () => Window | null;
|
|
11
|
+
/** Configured storefront origin; inbound messages are validated (pinned) against it. */
|
|
12
|
+
storefrontOrigin: string;
|
|
13
|
+
/** Post the contentless handshake reply to the (validated) origin. */
|
|
14
|
+
postInit: (frameWindow: Window, targetOrigin: string) => void;
|
|
15
|
+
onResize?: (height: number) => void;
|
|
16
|
+
onPageLoaded?: (payload: BridgePageLoadedPayload) => void;
|
|
17
|
+
onCheckoutFinish?: (orderId: string) => void;
|
|
18
|
+
onPwRecovery?: () => void;
|
|
19
|
+
onAuthChanged?: (payload: AuthChangedPayload) => void;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* The parent side of the storefront `postMessage` bridge, as a pure message dispatcher.
|
|
23
|
+
*
|
|
24
|
+
* Every inbound message must clear three gates before it is acted on:
|
|
25
|
+
* 1. **Identity** — `event.source` is our embedded frame's window (not some other frame).
|
|
26
|
+
* 2. **Origin** — `event.origin` matches the configured storefront origin (the pin).
|
|
27
|
+
* 3. **Envelope** — `parseBridgeMessage` accepts the `{ source, version, type, payload }`.
|
|
28
|
+
*
|
|
29
|
+
* On the storefront's contentless `laioutr:ready` ping it replies `laioutr:init` to the
|
|
30
|
+
* validated origin, which unblocks the plugin's buffered data messages.
|
|
31
|
+
*
|
|
32
|
+
* `ready` is a **one-shot** broadcast, and the server-rendered iframe can fire it before
|
|
33
|
+
* this listener is attached (the browser loads the iframe before Vue hydrates). So the
|
|
34
|
+
* handshake must not depend on catching it: {@link sendInit} lets the caller *proactively*
|
|
35
|
+
* post `init` to the configured origin once the frame has loaded. Both paths are
|
|
36
|
+
* idempotent — the plugin just re-pins and re-flushes.
|
|
37
|
+
*
|
|
38
|
+
* All logic lives here (no `window`, no Vue) so it is unit-testable; `useShopwareEmbedBridge`
|
|
39
|
+
* only wires it to the real `window` `message` event and the component lifecycle.
|
|
40
|
+
*/
|
|
41
|
+
export declare const createBridgeHandler: (options: CreateBridgeHandlerOptions) => {
|
|
42
|
+
handleMessage: (event: BridgeMessageEvent) => void;
|
|
43
|
+
sendInit: () => void;
|
|
44
|
+
};
|