@scayle/storefront-nuxt 7.36.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.
Files changed (135) hide show
  1. package/CHANGELOG.md +598 -0
  2. package/LICENSE +21 -0
  3. package/README.md +67 -0
  4. package/dist/index.d.mts +13 -0
  5. package/dist/index.d.ts +13 -0
  6. package/dist/index.mjs +2 -0
  7. package/dist/module.cjs +5 -0
  8. package/dist/module.d.mts +8 -0
  9. package/dist/module.d.ts +8 -0
  10. package/dist/module.json +8 -0
  11. package/dist/module.mjs +173 -0
  12. package/dist/rpc.d.mts +12 -0
  13. package/dist/rpc.d.ts +12 -0
  14. package/dist/rpc.mjs +18 -0
  15. package/dist/runtime/api/cacheAuth.d.ts +12 -0
  16. package/dist/runtime/api/cacheAuth.mjs +40 -0
  17. package/dist/runtime/api/purgeAll.d.ts +2 -0
  18. package/dist/runtime/api/purgeAll.mjs +14 -0
  19. package/dist/runtime/api/purgeTags.d.ts +2 -0
  20. package/dist/runtime/api/purgeTags.mjs +19 -0
  21. package/dist/runtime/api/rpcHandler.d.ts +2 -0
  22. package/dist/runtime/api/rpcHandler.mjs +15 -0
  23. package/dist/runtime/api/up.d.ts +4 -0
  24. package/dist/runtime/api/up.mjs +4 -0
  25. package/dist/runtime/composables/core/useAvailableShops.d.ts +2 -0
  26. package/dist/runtime/composables/core/useAvailableShops.mjs +4 -0
  27. package/dist/runtime/composables/core/useCurrentShop.d.ts +3 -0
  28. package/dist/runtime/composables/core/useCurrentShop.mjs +8 -0
  29. package/dist/runtime/composables/core/useEventListener.d.ts +2 -0
  30. package/dist/runtime/composables/core/useEventListener.mjs +2 -0
  31. package/dist/runtime/composables/core/useLog.d.ts +1 -0
  32. package/dist/runtime/composables/core/useLog.mjs +8 -0
  33. package/dist/runtime/composables/core/useRpc.d.ts +17 -0
  34. package/dist/runtime/composables/core/useRpc.mjs +56 -0
  35. package/dist/runtime/composables/core/useSession.d.ts +21 -0
  36. package/dist/runtime/composables/core/useSession.mjs +21 -0
  37. package/dist/runtime/composables/core/useUser.d.ts +16 -0
  38. package/dist/runtime/composables/core/useUser.mjs +68 -0
  39. package/dist/runtime/composables/storefront/useBasket.d.ts +54 -0
  40. package/dist/runtime/composables/storefront/useBasket.mjs +165 -0
  41. package/dist/runtime/composables/storefront/useBrand.d.ts +16 -0
  42. package/dist/runtime/composables/storefront/useBrand.mjs +20 -0
  43. package/dist/runtime/composables/storefront/useBrands.d.ts +16 -0
  44. package/dist/runtime/composables/storefront/useBrands.mjs +20 -0
  45. package/dist/runtime/composables/storefront/useCategories.d.ts +20 -0
  46. package/dist/runtime/composables/storefront/useCategories.mjs +30 -0
  47. package/dist/runtime/composables/storefront/useCurrentPromotions.d.ts +16 -0
  48. package/dist/runtime/composables/storefront/useCurrentPromotions.mjs +20 -0
  49. package/dist/runtime/composables/storefront/useFacet.d.ts +48 -0
  50. package/dist/runtime/composables/storefront/useFacet.mjs +193 -0
  51. package/dist/runtime/composables/storefront/useFilters.d.ts +16 -0
  52. package/dist/runtime/composables/storefront/useFilters.mjs +20 -0
  53. package/dist/runtime/composables/storefront/useNavigationTree.d.ts +16 -0
  54. package/dist/runtime/composables/storefront/useNavigationTree.mjs +20 -0
  55. package/dist/runtime/composables/storefront/useNavigationTrees.d.ts +16 -0
  56. package/dist/runtime/composables/storefront/useNavigationTrees.mjs +20 -0
  57. package/dist/runtime/composables/storefront/useOrder.d.ts +89 -0
  58. package/dist/runtime/composables/storefront/useOrder.mjs +17 -0
  59. package/dist/runtime/composables/storefront/useOrderConfirmation.d.ts +89 -0
  60. package/dist/runtime/composables/storefront/useOrderConfirmation.mjs +17 -0
  61. package/dist/runtime/composables/storefront/useProduct.d.ts +16 -0
  62. package/dist/runtime/composables/storefront/useProduct.mjs +20 -0
  63. package/dist/runtime/composables/storefront/useProducts.d.ts +20 -0
  64. package/dist/runtime/composables/storefront/useProducts.mjs +20 -0
  65. package/dist/runtime/composables/storefront/useProductsByIds.d.ts +16 -0
  66. package/dist/runtime/composables/storefront/useProductsByIds.mjs +20 -0
  67. package/dist/runtime/composables/storefront/useProductsCount.d.ts +16 -0
  68. package/dist/runtime/composables/storefront/useProductsCount.mjs +20 -0
  69. package/dist/runtime/composables/storefront/usePromotions.d.ts +16 -0
  70. package/dist/runtime/composables/storefront/usePromotions.mjs +20 -0
  71. package/dist/runtime/composables/storefront/usePromotionsByIds.d.ts +16 -0
  72. package/dist/runtime/composables/storefront/usePromotionsByIds.mjs +20 -0
  73. package/dist/runtime/composables/storefront/useQueryFilterState.d.ts +12 -0
  74. package/dist/runtime/composables/storefront/useQueryFilterState.mjs +66 -0
  75. package/dist/runtime/composables/storefront/useSearch.d.ts +13 -0
  76. package/dist/runtime/composables/storefront/useSearch.mjs +49 -0
  77. package/dist/runtime/composables/storefront/useShopConfiguration.d.ts +13 -0
  78. package/dist/runtime/composables/storefront/useShopConfiguration.mjs +19 -0
  79. package/dist/runtime/composables/storefront/useUserAddresses.d.ts +13 -0
  80. package/dist/runtime/composables/storefront/useUserAddresses.mjs +19 -0
  81. package/dist/runtime/composables/storefront/useVariant.d.ts +16 -0
  82. package/dist/runtime/composables/storefront/useVariant.mjs +20 -0
  83. package/dist/runtime/composables/storefront/useWishlist.d.ts +48 -0
  84. package/dist/runtime/composables/storefront/useWishlist.mjs +119 -0
  85. package/dist/runtime/composables/useCoreLog.d.ts +1 -0
  86. package/dist/runtime/composables/useCoreLog.mjs +4 -0
  87. package/dist/runtime/context.d.ts +12 -0
  88. package/dist/runtime/context.mjs +98 -0
  89. package/dist/runtime/createLog.d.ts +3 -0
  90. package/dist/runtime/createLog.mjs +39 -0
  91. package/dist/runtime/error/handler.d.ts +14 -0
  92. package/dist/runtime/error/handler.mjs +53 -0
  93. package/dist/runtime/handler.d.ts +8 -0
  94. package/dist/runtime/handler.mjs +26 -0
  95. package/dist/runtime/log.d.ts +38 -0
  96. package/dist/runtime/log.mjs +68 -0
  97. package/dist/runtime/plugin/log.client.d.ts +8 -0
  98. package/dist/runtime/plugin/log.client.mjs +24 -0
  99. package/dist/runtime/plugin/log.server.d.ts +2 -0
  100. package/dist/runtime/plugin/log.server.mjs +11 -0
  101. package/dist/runtime/plugin/shop.d.ts +8 -0
  102. package/dist/runtime/plugin/shop.mjs +17 -0
  103. package/dist/runtime/rpc/rpcCall.d.ts +9 -0
  104. package/dist/runtime/rpc/rpcCall.mjs +16 -0
  105. package/dist/runtime/server/middleware/bootstrap.d.ts +12 -0
  106. package/dist/runtime/server/middleware/bootstrap.mjs +188 -0
  107. package/dist/runtime/server/middleware/bootstrap.test.d.ts +6 -0
  108. package/dist/runtime/server/middleware/bootstrap.test.mjs +277 -0
  109. package/dist/runtime/server/middleware/bootstrap.utils.d.ts +7 -0
  110. package/dist/runtime/server/middleware/bootstrap.utils.mjs +59 -0
  111. package/dist/runtime/server/middleware/cache.d.ts +12 -0
  112. package/dist/runtime/server/middleware/cache.mjs +44 -0
  113. package/dist/runtime/server/middleware/oauth.d.ts +3 -0
  114. package/dist/runtime/server/middleware/oauth.mjs +123 -0
  115. package/dist/runtime/server/middleware/redirects.d.ts +2 -0
  116. package/dist/runtime/server/middleware/redirects.mjs +86 -0
  117. package/dist/runtime/server/middleware/redirects.test.d.ts +1 -0
  118. package/dist/runtime/server/middleware/redirects.test.mjs +66 -0
  119. package/dist/runtime/server/middleware/redirects.utils.d.ts +18 -0
  120. package/dist/runtime/server/middleware/redirects.utils.mjs +28 -0
  121. package/dist/runtime/utils/error.d.ts +5 -0
  122. package/dist/runtime/utils/error.mjs +7 -0
  123. package/dist/runtime/utils/price.d.ts +11 -0
  124. package/dist/runtime/utils/price.mjs +32 -0
  125. package/dist/runtime/utils/price.test.d.ts +1 -0
  126. package/dist/runtime/utils/price.test.mjs +83 -0
  127. package/dist/runtime/utils/route.d.ts +2 -0
  128. package/dist/runtime/utils/route.mjs +3 -0
  129. package/dist/runtime/utils/route.test.d.ts +1 -0
  130. package/dist/runtime/utils/route.test.mjs +15 -0
  131. package/dist/shared/storefront-nuxt.9548700f.d.mts +161 -0
  132. package/dist/shared/storefront-nuxt.9548700f.d.ts +161 -0
  133. package/dist/types.d.mts +15 -0
  134. package/dist/types.d.ts +15 -0
  135. package/package.json +103 -0
@@ -0,0 +1,11 @@
1
+ import { defineNuxtPlugin } from "nuxt/app";
2
+ export default defineNuxtPlugin((nuxtApp) => {
3
+ if (!nuxtApp.ssrContext) {
4
+ return;
5
+ }
6
+ const log = nuxtApp.ssrContext.event.context.$rpcContext.log;
7
+ const coreLog = log.space("sfc");
8
+ nuxtApp.provide("log", log);
9
+ nuxtApp.provide("coreLog", coreLog);
10
+ coreLog.debug("Attached server-side logger");
11
+ });
@@ -0,0 +1,8 @@
1
+ declare const _default: import("nuxt/app").Plugin<{
2
+ currentShop: import("../../module").PublicShopConfig;
3
+ availableShops: import("../../module").PublicShopConfig[];
4
+ }> & import("nuxt/app").ObjectPlugin<{
5
+ currentShop: import("../../module").PublicShopConfig;
6
+ availableShops: import("../../module").PublicShopConfig[];
7
+ }>;
8
+ export default _default;
@@ -0,0 +1,17 @@
1
+ import { defineNuxtPlugin } from "nuxt/app";
2
+ import { useCurrentShop } from "../composables/core/useCurrentShop.mjs";
3
+ import { useAvailableShops } from "../composables/core/useAvailableShops.mjs";
4
+ export default defineNuxtPlugin((nuxtApp) => {
5
+ const currentShop = useCurrentShop();
6
+ const availableShops = useAvailableShops();
7
+ if (process.server && nuxtApp.ssrContext) {
8
+ currentShop.value = nuxtApp.ssrContext.event.context.$currentShop;
9
+ availableShops.value = nuxtApp.ssrContext.event.context.$availableShops;
10
+ }
11
+ return {
12
+ provide: {
13
+ currentShop: currentShop.value,
14
+ availableShops: availableShops.value
15
+ }
16
+ };
17
+ });
@@ -0,0 +1,9 @@
1
+ import type { RpcContext, RpcMethodName, RpcMethodParameters, RpcMethodReturnType } from '@scayle/storefront-core';
2
+ import type { NuxtApp } from 'nuxt/app';
3
+ import type { PublicShopConfig } from '../../types/module';
4
+ /**
5
+ * rpcCall can be used to invoke an RPC method either on the client or server
6
+ * It makes an HTTP request on the client and direct call on the server using $fetch
7
+ * https://nuxt.com/docs/api/utils/dollarfetch
8
+ */
9
+ export declare const rpcCall: <N extends RpcMethodName, P extends RpcMethodParameters<N>, TResult extends Awaited<RpcMethodReturnType<N>>, TakesParameters = P extends RpcContext ? undefined : boolean>(nuxtApp: NuxtApp, method: N, shop: PublicShopConfig) => TakesParameters extends undefined ? () => Promise<TResult> : (params: P) => Promise<TResult>;
@@ -0,0 +1,16 @@
1
+ export const rpcCall = (nuxtApp, method, shop) => {
2
+ const fetch = nuxtApp.ssrContext?.event.$fetch ?? $fetch;
3
+ return async (params = void 0) => {
4
+ const data = await fetch(`${shop.apiBasePath ?? "/api"}/rpc/${method}`, {
5
+ // @ts-ignore
6
+ method: "POST",
7
+ body: {
8
+ payload: params
9
+ },
10
+ headers: {
11
+ "x-shop-id": shop.shopId + ""
12
+ }
13
+ });
14
+ return data;
15
+ };
16
+ };
@@ -0,0 +1,12 @@
1
+ import { RpcContext, Cache } from '@scayle/storefront-core';
2
+ import type { PublicShopConfig } from '../../../module';
3
+ declare module 'h3' {
4
+ interface H3EventContext {
5
+ $rpcContext: RpcContext;
6
+ $cache: Cache;
7
+ $currentShop: PublicShopConfig;
8
+ $availableShops: PublicShopConfig[];
9
+ }
10
+ }
11
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
12
+ export default _default;
@@ -0,0 +1,188 @@
1
+ import { defineEventHandler, getRequestURL } from "h3";
2
+ import { createConsola } from "consola";
3
+ import { createRemoteJWKSet, decodeJwt } from "jose";
4
+ import { randomUUID } from "uncrypto";
5
+ import redisDriver from "unstorage/drivers/redis";
6
+ import memoryDriver from "unstorage/drivers/memory";
7
+ import { createStorage } from "unstorage";
8
+ import {
9
+ useSession,
10
+ UnstorageSessionStore
11
+ } from "@scayle/h3-session";
12
+ import { buildContext } from "../../context.mjs";
13
+ import createLog from "../../createLog.mjs";
14
+ import {
15
+ convertStoresToList,
16
+ getCurrentShopForRequest,
17
+ getPublicShopData
18
+ } from "./bootstrap.utils.mjs";
19
+ import { useOauth } from "./oauth.mjs";
20
+ import { useRedirects } from "./redirects.mjs";
21
+ import { createCache } from "./cache.mjs";
22
+ import { useRuntimeConfig } from "#imports";
23
+ const getShopConfig = (event, config) => {
24
+ const storefrontConfig = config.storefront;
25
+ const $shopConfig = getCurrentShopForRequest(
26
+ event,
27
+ storefrontConfig
28
+ );
29
+ return $shopConfig;
30
+ };
31
+ const memoryStorage = memoryDriver();
32
+ function createSessionStore(sessionConfig, redisConfig, log) {
33
+ const ioredisConfig = {
34
+ ..."user" in redisConfig && {
35
+ username: redisConfig.user
36
+ },
37
+ ..."password" in redisConfig && {
38
+ password: redisConfig.password
39
+ },
40
+ host: redisConfig.host,
41
+ port: redisConfig.port,
42
+ db: 1,
43
+ tls: redisConfig.sslTransit ? {} : void 0
44
+ };
45
+ if (sessionConfig.provider === "memory") {
46
+ log.warn("Creating in-memory session storage");
47
+ log.warn("In-memory storage should not be used in production");
48
+ return new UnstorageSessionStore(
49
+ createStorage({
50
+ driver: memoryStorage
51
+ })
52
+ );
53
+ }
54
+ log.debug(
55
+ `Creating redis session storage. host: ${ioredisConfig.host} port: ${ioredisConfig.port} db: ${ioredisConfig.db} tls: ${ioredisConfig.tls}`
56
+ );
57
+ return new UnstorageSessionStore(
58
+ createStorage({
59
+ driver: redisDriver(ioredisConfig)
60
+ }),
61
+ {
62
+ ttl: sessionConfig?.maxAge ?? 60 * 60 * 24
63
+ }
64
+ );
65
+ }
66
+ async function handleOauth(event, storefrontConfig, log) {
67
+ if (!storefrontConfig.oauth.apiHost) {
68
+ log.error("oauth.apiHost must be set in the module options.");
69
+ return false;
70
+ }
71
+ if (!storefrontConfig.oauth.clientId) {
72
+ log.error("oauth.clientId must be set in the module options.");
73
+ return false;
74
+ }
75
+ if (!storefrontConfig.oauth.clientSecret) {
76
+ log.error("oauth.clientSecret must be set in the module options.");
77
+ return false;
78
+ }
79
+ const authURL = storefrontConfig.oauth.apiHost;
80
+ const JWKS = createRemoteJWKSet(new URL("/.well-known/jwks.json", authURL), {
81
+ cacheMaxAge: 5 * 60 * 1e3
82
+ // Cache Keys for 5 minutes.
83
+ });
84
+ await useOauth(event, JWKS);
85
+ }
86
+ async function bootstrap(event, log, url, $shopConfig, $storefrontConfig, apiBasePath) {
87
+ const bootstrapLog = log.space("bootstrap");
88
+ bootstrapLog.debug("Bootstrapping request: ", url.toString());
89
+ const redisConfig = $shopConfig.redis ?? $storefrontConfig.redis;
90
+ const $cache = createCache(
91
+ $storefrontConfig.cache?.provider ?? "redis",
92
+ String($shopConfig.shopId),
93
+ bootstrapLog,
94
+ { redis: redisConfig }
95
+ );
96
+ const sessionDefaults = {
97
+ maxAge: void 0,
98
+ cookieName: "$session",
99
+ secret: "current-secret",
100
+ sameSite: "none"
101
+ };
102
+ const sessionConfig = {
103
+ ...sessionDefaults,
104
+ ...$storefrontConfig.session,
105
+ ...$shopConfig.sessionConfig
106
+ };
107
+ const sessionStorage = createSessionStore(sessionConfig, redisConfig, log);
108
+ const generateSessionId = (event2) => {
109
+ let userId;
110
+ if (event2?.context.session?.data?.accessToken) {
111
+ const payload = decodeJwt(event2?.context.session?.data?.accessToken);
112
+ userId = payload.customerId;
113
+ }
114
+ return userId ? `${userId}:${randomUUID()}` : randomUUID();
115
+ };
116
+ bootstrapLog.debug("Attaching session to request");
117
+ await useSession(event, {
118
+ ...sessionConfig,
119
+ store: sessionStorage,
120
+ // @ts-ignore
121
+ genid: generateSessionId,
122
+ saveUninitialized: true,
123
+ cookie: {
124
+ maxAge: sessionConfig?.maxAge,
125
+ // secure: sessionConfig.isHttps, TODO
126
+ sameSite: sessionConfig.sameSite,
127
+ domain: sessionConfig?.domain
128
+ },
129
+ name: sessionConfig?.cookieName ?? "$session",
130
+ secret: sessionConfig?.secret ?? "current-secret"
131
+ });
132
+ event.context.$currentShop = getPublicShopData(
133
+ $shopConfig,
134
+ $shopConfig.shopId,
135
+ apiBasePath,
136
+ $storefrontConfig.publicShopData
137
+ );
138
+ event.context.$availableShops = convertStoresToList($storefrontConfig.stores).filter((store) => store.isEnabled !== false).map(
139
+ (config) => getPublicShopData(
140
+ config,
141
+ $shopConfig.shopId,
142
+ apiBasePath,
143
+ $storefrontConfig.publicShopData
144
+ )
145
+ );
146
+ event.context.$cache = $cache;
147
+ bootstrapLog.debug("Building context for request");
148
+ event.context.$rpcContext = await buildContext({
149
+ $cache,
150
+ $shopConfig,
151
+ $storefront: $storefrontConfig,
152
+ $log: log,
153
+ sessionId: event.context.sessionId,
154
+ session: event.context.session
155
+ });
156
+ }
157
+ export default defineEventHandler(async (event) => {
158
+ try {
159
+ const url = getRequestURL(event);
160
+ if (url.pathname.startsWith("/__nuxt")) {
161
+ return;
162
+ }
163
+ const config = useRuntimeConfig();
164
+ const $storefrontConfig = config.storefront;
165
+ const $shopConfig = getShopConfig(event, config);
166
+ const apiBasePath = $shopConfig.apiBasePath || $storefrontConfig.apiBasePath || "/api";
167
+ if (url.pathname === `${apiBasePath}/up`) {
168
+ return;
169
+ }
170
+ const log = createLog(createConsola, $storefrontConfig.log.name);
171
+ if (!event.context.$rpcContext) {
172
+ await bootstrap(
173
+ event,
174
+ log,
175
+ url,
176
+ $shopConfig,
177
+ $storefrontConfig,
178
+ apiBasePath
179
+ );
180
+ }
181
+ if ($storefrontConfig.redirects?.enabled && !url.pathname.startsWith(apiBasePath)) {
182
+ await useRedirects(event);
183
+ }
184
+ await handleOauth(event, $storefrontConfig, log);
185
+ } catch (e) {
186
+ console.error(e);
187
+ }
188
+ });
@@ -0,0 +1,6 @@
1
+ declare module '../../../module' {
2
+ interface AdditionalShopConfig {
3
+ paymentProviders: string[];
4
+ }
5
+ }
6
+ export {};
@@ -0,0 +1,277 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { createRequest, createResponse } from "node-mocks-http";
3
+ import { createEvent } from "h3";
4
+ import { getCurrentShopForRequest, getPublicShopData } from "./bootstrap.utils.mjs";
5
+ const DOMAIN_DE = "de.scayle.example";
6
+ const DOMAIN_AT = "at.scayle.example";
7
+ const DOMAIN_US = "us.scayle.example";
8
+ const CHECKOUT_HOST = "https://checkout.scayle.cloud";
9
+ const baseShop = {
10
+ appKeys: {
11
+ wishlistKey: "wishlist_{shopId}_{userId}",
12
+ basketKey: "basket_{shopId}_{userId}",
13
+ hashAlgorithm: "sha256"
14
+ },
15
+ auth: {
16
+ resetPasswordUrl: ""
17
+ },
18
+ domain: "demo.test",
19
+ paymentProviders: [],
20
+ checkout: {
21
+ host: CHECKOUT_HOST,
22
+ token: "",
23
+ secret: "",
24
+ user: ""
25
+ }
26
+ };
27
+ describe("getCurrentShopForRequest", () => {
28
+ it("respects the x-shop-id header", () => {
29
+ const req = createRequest({
30
+ url: "/api/getProducts",
31
+ headers: {
32
+ "x-shop-id": "1002"
33
+ }
34
+ });
35
+ const moduleOptions = {
36
+ shopSelector: "domain",
37
+ stores: {
38
+ DE_DE: {
39
+ ...baseShop,
40
+ shopId: 1001,
41
+ domain: DOMAIN_DE,
42
+ locale: "de-DE",
43
+ currency: "EUR"
44
+ },
45
+ DE_AT: {
46
+ ...baseShop,
47
+ shopId: 1002,
48
+ domain: DOMAIN_AT,
49
+ locale: "de-AT",
50
+ currency: "EUR"
51
+ },
52
+ DE_US: {
53
+ ...baseShop,
54
+ shopId: 1003,
55
+ domain: DOMAIN_US,
56
+ locale: "en-US",
57
+ currency: "USD"
58
+ }
59
+ }
60
+ };
61
+ const shop = getCurrentShopForRequest(
62
+ createEvent(req, createResponse()),
63
+ moduleOptions
64
+ );
65
+ expect(shop.shopId).to.eq(1002);
66
+ });
67
+ it("falls back to the path when shopSelector = path", () => {
68
+ const moduleOptions = {
69
+ shopSelector: "path",
70
+ stores: {
71
+ DE_DE: {
72
+ ...baseShop,
73
+ shopId: 1001,
74
+ path: "de",
75
+ locale: "de-DE",
76
+ currency: "EUR"
77
+ },
78
+ DE_AT: {
79
+ ...baseShop,
80
+ shopId: 1002,
81
+ path: "at",
82
+ locale: "de-AT",
83
+ currency: "EUR"
84
+ },
85
+ EN_US: {
86
+ ...baseShop,
87
+ shopId: 1003,
88
+ path: "foobar",
89
+ locale: "en-US",
90
+ currency: "USD"
91
+ },
92
+ DE_CH: {
93
+ ...baseShop,
94
+ shopId: 1004,
95
+ path: "de-ch",
96
+ locale: "de-CH",
97
+ currency: "CHF"
98
+ }
99
+ }
100
+ };
101
+ expect(
102
+ getCurrentShopForRequest(
103
+ createEvent(
104
+ createRequest({
105
+ url: "https://de.scayle.example/foobar/men/shirts"
106
+ }),
107
+ createResponse()
108
+ ),
109
+ moduleOptions
110
+ ).shopId
111
+ ).to.eq(1003);
112
+ expect(
113
+ getCurrentShopForRequest(
114
+ createEvent(
115
+ createRequest({
116
+ url: "/foobar/men/shirts"
117
+ }),
118
+ createResponse()
119
+ ),
120
+ moduleOptions
121
+ ).shopId
122
+ ).to.eq(1003);
123
+ expect(
124
+ getCurrentShopForRequest(
125
+ createEvent(
126
+ createRequest({
127
+ url: "https://de.scayle.example/at/men/shirts"
128
+ }),
129
+ createResponse()
130
+ ),
131
+ moduleOptions
132
+ ).shopId
133
+ ).to.eq(1002);
134
+ expect(
135
+ getCurrentShopForRequest(
136
+ createEvent(
137
+ createRequest({
138
+ url: "https://de.scayle.example/de-ch/men/shirts"
139
+ }),
140
+ createResponse()
141
+ ),
142
+ moduleOptions
143
+ ).shopId
144
+ ).to.eq(1004);
145
+ });
146
+ it("falls back to the domain when shopSelector = domain", () => {
147
+ const req = createRequest({
148
+ url: "https://de.scayle.example/at/frauen"
149
+ });
150
+ const moduleOptions = {
151
+ shopSelector: "domain",
152
+ stores: {
153
+ DE_DE: {
154
+ ...baseShop,
155
+ shopId: 1001,
156
+ domain: DOMAIN_DE,
157
+ locale: "de-DE",
158
+ currency: "EUR"
159
+ },
160
+ DE_AT: {
161
+ ...baseShop,
162
+ shopId: 1002,
163
+ domain: DOMAIN_AT,
164
+ locale: "de-AT",
165
+ currency: "EUR"
166
+ },
167
+ EN_US: {
168
+ ...baseShop,
169
+ shopId: 1003,
170
+ domain: DOMAIN_US,
171
+ locale: "en-US",
172
+ currency: "USD"
173
+ }
174
+ }
175
+ };
176
+ const shop = getCurrentShopForRequest(
177
+ createEvent(req, createResponse()),
178
+ moduleOptions
179
+ );
180
+ expect(shop.shopId).to.eq(1001);
181
+ });
182
+ it("falls back to the first shop when there is no match", () => {
183
+ const req = createRequest({
184
+ url: "https://de.scayle.example/foobar/men/shirts",
185
+ headers: {
186
+ "x-shop-id": "1005"
187
+ }
188
+ });
189
+ const moduleOptions = {
190
+ shopSelector: "domain",
191
+ stores: {
192
+ DE_DE: {
193
+ ...baseShop,
194
+ shopId: 1001,
195
+ domain: DOMAIN_DE,
196
+ locale: "de-DE",
197
+ currency: "EUR"
198
+ },
199
+ DE_AT: {
200
+ ...baseShop,
201
+ shopId: 1002,
202
+ domain: DOMAIN_AT,
203
+ locale: "de-AT",
204
+ currency: "EUR"
205
+ },
206
+ EN_US: {
207
+ ...baseShop,
208
+ shopId: 1003,
209
+ domain: DOMAIN_US,
210
+ locale: "en-US",
211
+ currency: "USD"
212
+ }
213
+ }
214
+ };
215
+ const shop = getCurrentShopForRequest(
216
+ createEvent(req, createResponse()),
217
+ moduleOptions
218
+ );
219
+ expect(shop.shopId).to.eq(1001);
220
+ });
221
+ });
222
+ describe("getPublicShopData", () => {
223
+ it("excludes checkout secrets from the public shop data", () => {
224
+ const shopConfig = {
225
+ ...baseShop,
226
+ shopId: 1003,
227
+ domain: DOMAIN_US,
228
+ path: "en",
229
+ locale: "en-US",
230
+ currency: "USD"
231
+ };
232
+ expect(getPublicShopData(shopConfig, 1003, "/api")).to.eql({
233
+ shopId: 1003,
234
+ domain: DOMAIN_US,
235
+ path: "en",
236
+ locale: "en-US",
237
+ currency: "USD",
238
+ isActive: true,
239
+ checkout: {
240
+ host: CHECKOUT_HOST
241
+ },
242
+ apiBasePath: "/api"
243
+ });
244
+ });
245
+ it("includes additional shop data for keys in storefront.publicShopData", () => {
246
+ const shopConfig = {
247
+ ...baseShop,
248
+ shopId: 1003,
249
+ domain: DOMAIN_US,
250
+ path: "en",
251
+ locale: "en-US",
252
+ currency: "USD",
253
+ paymentProviders: ["visa", "klarna"],
254
+ checkout: {
255
+ host: CHECKOUT_HOST,
256
+ token: "",
257
+ secret: "",
258
+ user: ""
259
+ }
260
+ };
261
+ expect(
262
+ getPublicShopData(shopConfig, 1003, "/api", ["paymentProviders"])
263
+ ).to.eql({
264
+ shopId: 1003,
265
+ domain: DOMAIN_US,
266
+ path: "en",
267
+ locale: "en-US",
268
+ currency: "USD",
269
+ paymentProviders: ["visa", "klarna"],
270
+ isActive: true,
271
+ checkout: {
272
+ host: CHECKOUT_HOST
273
+ },
274
+ apiBasePath: "/api"
275
+ });
276
+ });
277
+ });
@@ -0,0 +1,7 @@
1
+ import { H3Event } from 'h3';
2
+ import type { ModuleOptions, PublicShopConfig, ShopConfig, ShopConfigIndexed } from '../../../module';
3
+ export declare const convertStoresToList: (stores: ShopConfig[] | ShopConfigIndexed) => ShopConfig[];
4
+ export declare function getCurrentShopForRequest(event: H3Event, config: Pick<ModuleOptions, 'stores' | 'shopSelector'>): ShopConfig;
5
+ export declare function getPublicShopData(data: ShopConfig, currentShopId: number, apiBasePath: string, publicShopData?: (keyof ShopConfig)[]): PublicShopConfig & {
6
+ isActive: boolean;
7
+ };
@@ -0,0 +1,59 @@
1
+ import { getRequestHeaders, getRequestHost } from "h3";
2
+ import { isArray, listify } from "radash";
3
+ const getStoreByDomain = (event, stores) => {
4
+ const host = getRequestHost(event, { xForwardedHost: true });
5
+ return stores.find((s) => s.domain === host);
6
+ };
7
+ const getStoreByPath = (event, stores) => {
8
+ if (!event.path) {
9
+ return;
10
+ }
11
+ const url = new URL(
12
+ event.path,
13
+ `https://${getRequestHost(event, { xForwardedHost: true })}`
14
+ );
15
+ const prefix = url.pathname.split("/")[1];
16
+ return stores.find((shop) => prefix === shop.path);
17
+ };
18
+ export const convertStoresToList = (stores) => {
19
+ if (isArray(stores)) {
20
+ return stores;
21
+ }
22
+ return listify(stores, (_key, value) => ({ ...value }));
23
+ };
24
+ export function getCurrentShopForRequest(event, config) {
25
+ let $shopConfig;
26
+ const { shopSelector, stores } = config;
27
+ const storesList = convertStoresToList(stores);
28
+ const headers = getRequestHeaders(event);
29
+ const shopId = headers["x-shop-id"];
30
+ if (shopId) {
31
+ $shopConfig = storesList.find((s) => s.shopId === Number(shopId));
32
+ } else if (shopSelector === "domain") {
33
+ $shopConfig = getStoreByDomain(event, storesList);
34
+ } else if (shopSelector === "path") {
35
+ $shopConfig = getStoreByPath(event, storesList);
36
+ }
37
+ $shopConfig = $shopConfig || storesList[0];
38
+ if (!$shopConfig) {
39
+ throw new Error("Could not find shop config");
40
+ }
41
+ return $shopConfig;
42
+ }
43
+ export function getPublicShopData(data, currentShopId, apiBasePath, publicShopData = []) {
44
+ const additionalShopData = {};
45
+ publicShopData.forEach((key) => additionalShopData[key] = data[key]);
46
+ return {
47
+ domain: data.domain,
48
+ path: data.path,
49
+ locale: data.locale,
50
+ shopId: data.shopId,
51
+ currency: data.currency,
52
+ isActive: data.shopId === currentShopId,
53
+ checkout: {
54
+ host: data.checkout.host
55
+ },
56
+ apiBasePath,
57
+ ...additionalShopData
58
+ };
59
+ }
@@ -0,0 +1,12 @@
1
+ import { Cache as CacheInterface, Log } from '@scayle/storefront-core';
2
+ import { RedisConfig, StorageProvider } from '../../../types/module';
3
+ /**
4
+ * Create an unstorage-backed cache interface
5
+ * @param provider
6
+ * @param prefix
7
+ * @param log
8
+ * @param options
9
+ */
10
+ export declare function createCache(provider: StorageProvider, prefix: string, log: Log, options?: {
11
+ redis?: RedisConfig;
12
+ }): CacheInterface;
@@ -0,0 +1,44 @@
1
+ import redisDriver from "unstorage/drivers/redis";
2
+ import memoryDriver from "unstorage/drivers/memory";
3
+ import { UnstorageCache } from "@scayle/storefront-core/dist/cache/providers/unstorage";
4
+ import { createStorage } from "unstorage";
5
+ const memoryStorage = memoryDriver();
6
+ export function createCache(provider, prefix, log, options) {
7
+ if (provider === "memory") {
8
+ log.warn("Creating in-memory redirect cache");
9
+ log.warn("In-memory storage should not be used in production");
10
+ return new UnstorageCache(
11
+ createStorage({
12
+ driver: memoryStorage
13
+ }),
14
+ prefix
15
+ );
16
+ }
17
+ if (provider === "redis") {
18
+ const redisConfig = options?.redis;
19
+ if (!redisConfig) {
20
+ throw Error("options.redis must be specified for the redis provider");
21
+ }
22
+ const ioredisConfig = {
23
+ ..."user" in redisConfig && {
24
+ username: redisConfig.user
25
+ },
26
+ ..."password" in redisConfig && {
27
+ password: redisConfig.password
28
+ },
29
+ host: redisConfig.host,
30
+ port: redisConfig.port,
31
+ tls: redisConfig.sslTransit ? {} : void 0
32
+ };
33
+ log.debug(
34
+ `Creating redis redirect cache. host: ${ioredisConfig.host} port: ${ioredisConfig.port} db: ${ioredisConfig.db} tls: ${ioredisConfig.tls}`
35
+ );
36
+ return new UnstorageCache(
37
+ createStorage({
38
+ driver: redisDriver(ioredisConfig)
39
+ }),
40
+ prefix
41
+ );
42
+ }
43
+ throw Error("Unknown cache provider");
44
+ }
@@ -0,0 +1,3 @@
1
+ import { H3Event } from 'h3';
2
+ import { JWTVerifyGetKey } from 'jose';
3
+ export declare function useOauth(event: H3Event, jwks: JWTVerifyGetKey): Promise<void>;