@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.
Files changed (193) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +146 -0
  3. package/dist/module.d.mts +88 -0
  4. package/dist/module.json +9 -0
  5. package/dist/module.mjs +60 -0
  6. package/dist/runtime/app/components/ShopwareEmbedFrame.d.vue.ts +14 -0
  7. package/dist/runtime/app/components/ShopwareEmbedFrame.vue +57 -0
  8. package/dist/runtime/app/components/ShopwareEmbedFrame.vue.d.ts +14 -0
  9. package/dist/runtime/app/composables/useShopwareEmbedBridge.d.ts +24 -0
  10. package/dist/runtime/app/composables/useShopwareEmbedBridge.js +22 -0
  11. package/dist/runtime/app/const/bridge.d.ts +63 -0
  12. package/dist/runtime/app/const/bridge.js +9 -0
  13. package/dist/runtime/app/image/providers/shopware.d.ts +7 -0
  14. package/dist/runtime/app/image/providers/shopware.js +44 -0
  15. package/dist/runtime/app/lib/createBridgeHandler.d.ts +44 -0
  16. package/dist/runtime/app/lib/createBridgeHandler.js +40 -0
  17. package/dist/runtime/app/lib/isTrustedOrigin.d.ts +9 -0
  18. package/dist/runtime/app/lib/isTrustedOrigin.js +8 -0
  19. package/dist/runtime/app/lib/parseBridgeMessage.d.ts +11 -0
  20. package/dist/runtime/app/lib/parseBridgeMessage.js +34 -0
  21. package/dist/runtime/app/plugins/pagetypes.d.ts +7 -0
  22. package/dist/runtime/app/plugins/pagetypes.js +6 -0
  23. package/dist/runtime/app/public/app-shopware/image-placeholder.svg +7 -0
  24. package/dist/runtime/app/public/app-shopware/shopware-logo.svg +10 -0
  25. package/dist/runtime/app/sections/SectionShopwareCheckout.d.vue.ts +0 -0
  26. package/dist/runtime/app/sections/SectionShopwareCheckout.vue +44 -0
  27. package/dist/runtime/app/sections/SectionShopwareCheckout.vue.d.ts +0 -0
  28. package/dist/runtime/index.d.ts +1 -0
  29. package/dist/runtime/index.js +1 -0
  30. package/dist/runtime/server/client/shopwareAdminClientFactory.d.ts +4 -0
  31. package/dist/runtime/server/client/shopwareAdminClientFactory.js +13 -0
  32. package/dist/runtime/server/client/shopwareClientFactory.d.ts +3 -0
  33. package/dist/runtime/server/client/shopwareClientFactory.js +18 -0
  34. package/dist/runtime/server/composable/useGetProductParentId.d.ts +4 -0
  35. package/dist/runtime/server/composable/useGetProductParentId.js +41 -0
  36. package/dist/runtime/server/const/checkout.d.ts +7 -0
  37. package/dist/runtime/server/const/checkout.js +2 -0
  38. package/dist/runtime/server/const/cookieKeys.d.ts +1 -0
  39. package/dist/runtime/server/const/cookieKeys.js +1 -0
  40. package/dist/runtime/server/const/fallbacks.d.ts +5 -0
  41. package/dist/runtime/server/const/fallbacks.js +14 -0
  42. package/dist/runtime/server/const/includes.d.ts +1 -0
  43. package/dist/runtime/server/const/includes.js +1 -0
  44. package/dist/runtime/server/const/passthroughTokens.d.ts +14 -0
  45. package/dist/runtime/server/const/passthroughTokens.js +10 -0
  46. package/dist/runtime/server/media-libraries/shopware.d.ts +20 -0
  47. package/dist/runtime/server/media-libraries/shopware.js +90 -0
  48. package/dist/runtime/server/middleware/defineShopware.d.ts +7 -0
  49. package/dist/runtime/server/middleware/defineShopware.js +33 -0
  50. package/dist/runtime/server/orchestr/auth/login.action.d.ts +2 -0
  51. package/dist/runtime/server/orchestr/auth/login.action.js +17 -0
  52. package/dist/runtime/server/orchestr/cart/add-item.action.d.ts +2 -0
  53. package/dist/runtime/server/orchestr/cart/add-item.action.js +46 -0
  54. package/dist/runtime/server/orchestr/cart/base.resolver.d.ts +2 -0
  55. package/dist/runtime/server/orchestr/cart/base.resolver.js +28 -0
  56. package/dist/runtime/server/orchestr/cart/cart-items.link.d.ts +2 -0
  57. package/dist/runtime/server/orchestr/cart/cart-items.link.js +10 -0
  58. package/dist/runtime/server/orchestr/cart/get-checkout-url.action.d.ts +10 -0
  59. package/dist/runtime/server/orchestr/cart/get-checkout-url.action.js +6 -0
  60. package/dist/runtime/server/orchestr/cart/get-current.query.d.ts +2 -0
  61. package/dist/runtime/server/orchestr/cart/get-current.query.js +9 -0
  62. package/dist/runtime/server/orchestr/cart/remove-items.action.d.ts +2 -0
  63. package/dist/runtime/server/orchestr/cart/remove-items.action.js +14 -0
  64. package/dist/runtime/server/orchestr/cart/update-items.action.d.ts +2 -0
  65. package/dist/runtime/server/orchestr/cart/update-items.action.js +16 -0
  66. package/dist/runtime/server/orchestr/cart-item/base.resolver.d.ts +2 -0
  67. package/dist/runtime/server/orchestr/cart-item/base.resolver.js +33 -0
  68. package/dist/runtime/server/orchestr/category/all.query.d.ts +2 -0
  69. package/dist/runtime/server/orchestr/category/all.query.js +15 -0
  70. package/dist/runtime/server/orchestr/category/base.resolver.d.ts +2 -0
  71. package/dist/runtime/server/orchestr/category/base.resolver.js +47 -0
  72. package/dist/runtime/server/orchestr/category/bySlug.query.d.ts +2 -0
  73. package/dist/runtime/server/orchestr/category/bySlug.query.js +12 -0
  74. package/dist/runtime/server/orchestr/category/child-categories.link.d.ts +2 -0
  75. package/dist/runtime/server/orchestr/category/child-categories.link.js +36 -0
  76. package/dist/runtime/server/orchestr/category/listing-page.page-index.d.ts +2 -0
  77. package/dist/runtime/server/orchestr/category/listing-page.page-index.js +79 -0
  78. package/dist/runtime/server/orchestr/category/menu-item.link.d.ts +2 -0
  79. package/dist/runtime/server/orchestr/category/menu-item.link.js +10 -0
  80. package/dist/runtime/server/orchestr/menu/base.resolver.d.ts +2 -0
  81. package/dist/runtime/server/orchestr/menu/base.resolver.js +63 -0
  82. package/dist/runtime/server/orchestr/menu/byAlias.query.d.ts +2 -0
  83. package/dist/runtime/server/orchestr/menu/byAlias.query.js +29 -0
  84. package/dist/runtime/server/orchestr/menu/byAlias.template.d.ts +2 -0
  85. package/dist/runtime/server/orchestr/menu/byAlias.template.js +25 -0
  86. package/dist/runtime/server/orchestr/newsletter/subscribe.action.d.ts +2 -0
  87. package/dist/runtime/server/orchestr/newsletter/subscribe.action.js +27 -0
  88. package/dist/runtime/server/orchestr/product/base.resolver.d.ts +2 -0
  89. package/dist/runtime/server/orchestr/product/base.resolver.js +119 -0
  90. package/dist/runtime/server/orchestr/product/breadcrumb.link.d.ts +2 -0
  91. package/dist/runtime/server/orchestr/product/breadcrumb.link.js +57 -0
  92. package/dist/runtime/server/orchestr/product/byCategoryId.query.d.ts +2 -0
  93. package/dist/runtime/server/orchestr/product/byCategoryId.query.js +47 -0
  94. package/dist/runtime/server/orchestr/product/byCategorySlug.query.d.ts +2 -0
  95. package/dist/runtime/server/orchestr/product/byCategorySlug.query.js +59 -0
  96. package/dist/runtime/server/orchestr/product/byCategorySlug.template.d.ts +2 -0
  97. package/dist/runtime/server/orchestr/product/byCategorySlug.template.js +40 -0
  98. package/dist/runtime/server/orchestr/product/bySlug.query.d.ts +2 -0
  99. package/dist/runtime/server/orchestr/product/bySlug.query.js +19 -0
  100. package/dist/runtime/server/orchestr/product/detail-page.page-index.d.ts +3 -0
  101. package/dist/runtime/server/orchestr/product/detail-page.page-index.js +58 -0
  102. package/dist/runtime/server/orchestr/product/reviews.link.d.ts +2 -0
  103. package/dist/runtime/server/orchestr/product/reviews.link.js +27 -0
  104. package/dist/runtime/server/orchestr/product/search.query.d.ts +2 -0
  105. package/dist/runtime/server/orchestr/product/search.query.js +43 -0
  106. package/dist/runtime/server/orchestr/product/variants.link.d.ts +2 -0
  107. package/dist/runtime/server/orchestr/product/variants.link.js +18 -0
  108. package/dist/runtime/server/orchestr/product-variant/base.resolver.d.ts +2 -0
  109. package/dist/runtime/server/orchestr/product-variant/base.resolver.js +129 -0
  110. package/dist/runtime/server/orchestr/review/base.resolver.d.ts +2 -0
  111. package/dist/runtime/server/orchestr/review/base.resolver.js +38 -0
  112. package/dist/runtime/server/orchestr/review/create-review.action.d.ts +2 -0
  113. package/dist/runtime/server/orchestr/review/create-review.action.js +14 -0
  114. package/dist/runtime/server/orchestr/suggested-search/suggested-search-entry.link.d.ts +2 -0
  115. package/dist/runtime/server/orchestr/suggested-search/suggested-search-entry.link.js +14 -0
  116. package/dist/runtime/server/orchestr/suggested-search/suggested-search.query.d.ts +2 -0
  117. package/dist/runtime/server/orchestr/suggested-search/suggested-search.query.js +54 -0
  118. package/dist/runtime/server/orchestr/suggested-search-entry/base.resolver.d.ts +2 -0
  119. package/dist/runtime/server/orchestr/suggested-search-entry/base.resolver.js +22 -0
  120. package/dist/runtime/server/orchestr-helper/isLinkSingle.d.ts +2 -0
  121. package/dist/runtime/server/orchestr-helper/isLinkSingle.js +1 -0
  122. package/dist/runtime/server/orchestr-helper/matchAndMap.d.ts +1 -0
  123. package/dist/runtime/server/orchestr-helper/matchAndMap.js +15 -0
  124. package/dist/runtime/server/orchestr-helper/requestedFields.d.ts +18 -0
  125. package/dist/runtime/server/orchestr-helper/requestedFields.js +145 -0
  126. package/dist/runtime/server/routes/adopt-session.post.d.ts +8 -0
  127. package/dist/runtime/server/routes/adopt-session.post.js +16 -0
  128. package/dist/runtime/server/routes/checkout.d.ts +16 -0
  129. package/dist/runtime/server/routes/checkout.js +31 -0
  130. package/dist/runtime/server/shopware-helper/adoptSession.d.ts +18 -0
  131. package/dist/runtime/server/shopware-helper/adoptSession.js +23 -0
  132. package/dist/runtime/server/shopware-helper/bootstrapContextToken.d.ts +18 -0
  133. package/dist/runtime/server/shopware-helper/bootstrapContextToken.js +22 -0
  134. package/dist/runtime/server/shopware-helper/cartErrors.d.ts +11 -0
  135. package/dist/runtime/server/shopware-helper/cartErrors.js +21 -0
  136. package/dist/runtime/server/shopware-helper/cartMapper.d.ts +14 -0
  137. package/dist/runtime/server/shopware-helper/cartMapper.js +83 -0
  138. package/dist/runtime/server/shopware-helper/categoryFlattener.d.ts +75 -0
  139. package/dist/runtime/server/shopware-helper/categoryFlattener.js +11 -0
  140. package/dist/runtime/server/shopware-helper/checkoutUrl.d.ts +12 -0
  141. package/dist/runtime/server/shopware-helper/checkoutUrl.js +5 -0
  142. package/dist/runtime/server/shopware-helper/contextTokenCookie.d.ts +11 -0
  143. package/dist/runtime/server/shopware-helper/contextTokenCookie.js +7 -0
  144. package/dist/runtime/server/shopware-helper/facetMapper.d.ts +38 -0
  145. package/dist/runtime/server/shopware-helper/facetMapper.js +79 -0
  146. package/dist/runtime/server/shopware-helper/fetchAllProductVariants.d.ts +137 -0
  147. package/dist/runtime/server/shopware-helper/fetchAllProductVariants.js +16 -0
  148. package/dist/runtime/server/shopware-helper/getCart.d.ts +10 -0
  149. package/dist/runtime/server/shopware-helper/getCart.js +4 -0
  150. package/dist/runtime/server/shopware-helper/isShopwareId.d.ts +4 -0
  151. package/dist/runtime/server/shopware-helper/isShopwareId.js +2 -0
  152. package/dist/runtime/server/shopware-helper/mappers/slugMapper.d.ts +10 -0
  153. package/dist/runtime/server/shopware-helper/mappers/slugMapper.js +9 -0
  154. package/dist/runtime/server/shopware-helper/mediaMapper.d.ts +8 -0
  155. package/dist/runtime/server/shopware-helper/mediaMapper.js +42 -0
  156. package/dist/runtime/server/shopware-helper/pageIndexEntries.d.ts +20 -0
  157. package/dist/runtime/server/shopware-helper/pageIndexEntries.js +22 -0
  158. package/dist/runtime/server/shopware-helper/persistContextToken.d.ts +13 -0
  159. package/dist/runtime/server/shopware-helper/persistContextToken.js +12 -0
  160. package/dist/runtime/server/shopware-helper/productLocate.d.ts +14 -0
  161. package/dist/runtime/server/shopware-helper/productLocate.js +9 -0
  162. package/dist/runtime/server/shopware-helper/readCategoryPageMeta.d.ts +10 -0
  163. package/dist/runtime/server/shopware-helper/readCategoryPageMeta.js +19 -0
  164. package/dist/runtime/server/shopware-helper/readProductPageMeta.d.ts +10 -0
  165. package/dist/runtime/server/shopware-helper/readProductPageMeta.js +20 -0
  166. package/dist/runtime/server/shopware-helper/resolveAdoptSession.d.ts +23 -0
  167. package/dist/runtime/server/shopware-helper/resolveAdoptSession.js +13 -0
  168. package/dist/runtime/server/shopware-helper/resolveCheckout.d.ts +39 -0
  169. package/dist/runtime/server/shopware-helper/resolveCheckout.js +24 -0
  170. package/dist/runtime/server/shopware-helper/sessionHandoff.d.ts +27 -0
  171. package/dist/runtime/server/shopware-helper/sessionHandoff.js +29 -0
  172. package/dist/runtime/server/shopware-helper/sortingMapper.d.ts +3 -0
  173. package/dist/runtime/server/shopware-helper/sortingMapper.js +7 -0
  174. package/dist/runtime/server/shopware-helper/storeApiPageFetcher.d.ts +20 -0
  175. package/dist/runtime/server/shopware-helper/storeApiPageFetcher.js +10 -0
  176. package/dist/runtime/server/shopware-helper/swTranslated.d.ts +7 -0
  177. package/dist/runtime/server/shopware-helper/swTranslated.js +4 -0
  178. package/dist/runtime/server/shopware-helper/system/getCurrentSystemEntities.d.ts +20 -0
  179. package/dist/runtime/server/shopware-helper/system/getCurrentSystemEntities.js +39 -0
  180. package/dist/runtime/server/shopware-helper/system/getSystemEntities.d.ts +31 -0
  181. package/dist/runtime/server/shopware-helper/system/getSystemEntities.js +47 -0
  182. package/dist/runtime/server/shopware-helper/useSeoResolver.d.ts +11 -0
  183. package/dist/runtime/server/shopware-helper/useSeoResolver.js +70 -0
  184. package/dist/runtime/server/tsconfig.json +3 -0
  185. package/dist/runtime/server/types/shopware.d.ts +29 -0
  186. package/dist/runtime/server/types/shopware.js +0 -0
  187. package/dist/runtime/server/types/storeApiTypes.d.ts +5677 -0
  188. package/dist/runtime/shared/const/checkout.d.ts +18 -0
  189. package/dist/runtime/shared/const/checkout.js +2 -0
  190. package/dist/runtime/shared/pageTypes/checkout.pagetype.d.ts +21 -0
  191. package/dist/runtime/shared/pageTypes/checkout.pagetype.js +12 -0
  192. package/dist/types.d.mts +3 -0
  193. 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 };
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "@laioutr/app-shopware",
3
+ "version": "0.14.4",
4
+ "configKey": "@laioutr/app-shopware",
5
+ "builder": {
6
+ "@nuxt/module-builder": "1.0.1",
7
+ "unbuild": "3.6.1"
8
+ }
9
+ }
@@ -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
+ };