@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.
- package/CHANGELOG.md +598 -0
- package/LICENSE +21 -0
- package/README.md +67 -0
- package/dist/index.d.mts +13 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.mjs +2 -0
- package/dist/module.cjs +5 -0
- package/dist/module.d.mts +8 -0
- package/dist/module.d.ts +8 -0
- package/dist/module.json +8 -0
- package/dist/module.mjs +173 -0
- package/dist/rpc.d.mts +12 -0
- package/dist/rpc.d.ts +12 -0
- package/dist/rpc.mjs +18 -0
- package/dist/runtime/api/cacheAuth.d.ts +12 -0
- package/dist/runtime/api/cacheAuth.mjs +40 -0
- package/dist/runtime/api/purgeAll.d.ts +2 -0
- package/dist/runtime/api/purgeAll.mjs +14 -0
- package/dist/runtime/api/purgeTags.d.ts +2 -0
- package/dist/runtime/api/purgeTags.mjs +19 -0
- package/dist/runtime/api/rpcHandler.d.ts +2 -0
- package/dist/runtime/api/rpcHandler.mjs +15 -0
- package/dist/runtime/api/up.d.ts +4 -0
- package/dist/runtime/api/up.mjs +4 -0
- package/dist/runtime/composables/core/useAvailableShops.d.ts +2 -0
- package/dist/runtime/composables/core/useAvailableShops.mjs +4 -0
- package/dist/runtime/composables/core/useCurrentShop.d.ts +3 -0
- package/dist/runtime/composables/core/useCurrentShop.mjs +8 -0
- package/dist/runtime/composables/core/useEventListener.d.ts +2 -0
- package/dist/runtime/composables/core/useEventListener.mjs +2 -0
- package/dist/runtime/composables/core/useLog.d.ts +1 -0
- package/dist/runtime/composables/core/useLog.mjs +8 -0
- package/dist/runtime/composables/core/useRpc.d.ts +17 -0
- package/dist/runtime/composables/core/useRpc.mjs +56 -0
- package/dist/runtime/composables/core/useSession.d.ts +21 -0
- package/dist/runtime/composables/core/useSession.mjs +21 -0
- package/dist/runtime/composables/core/useUser.d.ts +16 -0
- package/dist/runtime/composables/core/useUser.mjs +68 -0
- package/dist/runtime/composables/storefront/useBasket.d.ts +54 -0
- package/dist/runtime/composables/storefront/useBasket.mjs +165 -0
- package/dist/runtime/composables/storefront/useBrand.d.ts +16 -0
- package/dist/runtime/composables/storefront/useBrand.mjs +20 -0
- package/dist/runtime/composables/storefront/useBrands.d.ts +16 -0
- package/dist/runtime/composables/storefront/useBrands.mjs +20 -0
- package/dist/runtime/composables/storefront/useCategories.d.ts +20 -0
- package/dist/runtime/composables/storefront/useCategories.mjs +30 -0
- package/dist/runtime/composables/storefront/useCurrentPromotions.d.ts +16 -0
- package/dist/runtime/composables/storefront/useCurrentPromotions.mjs +20 -0
- package/dist/runtime/composables/storefront/useFacet.d.ts +48 -0
- package/dist/runtime/composables/storefront/useFacet.mjs +193 -0
- package/dist/runtime/composables/storefront/useFilters.d.ts +16 -0
- package/dist/runtime/composables/storefront/useFilters.mjs +20 -0
- package/dist/runtime/composables/storefront/useNavigationTree.d.ts +16 -0
- package/dist/runtime/composables/storefront/useNavigationTree.mjs +20 -0
- package/dist/runtime/composables/storefront/useNavigationTrees.d.ts +16 -0
- package/dist/runtime/composables/storefront/useNavigationTrees.mjs +20 -0
- package/dist/runtime/composables/storefront/useOrder.d.ts +89 -0
- package/dist/runtime/composables/storefront/useOrder.mjs +17 -0
- package/dist/runtime/composables/storefront/useOrderConfirmation.d.ts +89 -0
- package/dist/runtime/composables/storefront/useOrderConfirmation.mjs +17 -0
- package/dist/runtime/composables/storefront/useProduct.d.ts +16 -0
- package/dist/runtime/composables/storefront/useProduct.mjs +20 -0
- package/dist/runtime/composables/storefront/useProducts.d.ts +20 -0
- package/dist/runtime/composables/storefront/useProducts.mjs +20 -0
- package/dist/runtime/composables/storefront/useProductsByIds.d.ts +16 -0
- package/dist/runtime/composables/storefront/useProductsByIds.mjs +20 -0
- package/dist/runtime/composables/storefront/useProductsCount.d.ts +16 -0
- package/dist/runtime/composables/storefront/useProductsCount.mjs +20 -0
- package/dist/runtime/composables/storefront/usePromotions.d.ts +16 -0
- package/dist/runtime/composables/storefront/usePromotions.mjs +20 -0
- package/dist/runtime/composables/storefront/usePromotionsByIds.d.ts +16 -0
- package/dist/runtime/composables/storefront/usePromotionsByIds.mjs +20 -0
- package/dist/runtime/composables/storefront/useQueryFilterState.d.ts +12 -0
- package/dist/runtime/composables/storefront/useQueryFilterState.mjs +66 -0
- package/dist/runtime/composables/storefront/useSearch.d.ts +13 -0
- package/dist/runtime/composables/storefront/useSearch.mjs +49 -0
- package/dist/runtime/composables/storefront/useShopConfiguration.d.ts +13 -0
- package/dist/runtime/composables/storefront/useShopConfiguration.mjs +19 -0
- package/dist/runtime/composables/storefront/useUserAddresses.d.ts +13 -0
- package/dist/runtime/composables/storefront/useUserAddresses.mjs +19 -0
- package/dist/runtime/composables/storefront/useVariant.d.ts +16 -0
- package/dist/runtime/composables/storefront/useVariant.mjs +20 -0
- package/dist/runtime/composables/storefront/useWishlist.d.ts +48 -0
- package/dist/runtime/composables/storefront/useWishlist.mjs +119 -0
- package/dist/runtime/composables/useCoreLog.d.ts +1 -0
- package/dist/runtime/composables/useCoreLog.mjs +4 -0
- package/dist/runtime/context.d.ts +12 -0
- package/dist/runtime/context.mjs +98 -0
- package/dist/runtime/createLog.d.ts +3 -0
- package/dist/runtime/createLog.mjs +39 -0
- package/dist/runtime/error/handler.d.ts +14 -0
- package/dist/runtime/error/handler.mjs +53 -0
- package/dist/runtime/handler.d.ts +8 -0
- package/dist/runtime/handler.mjs +26 -0
- package/dist/runtime/log.d.ts +38 -0
- package/dist/runtime/log.mjs +68 -0
- package/dist/runtime/plugin/log.client.d.ts +8 -0
- package/dist/runtime/plugin/log.client.mjs +24 -0
- package/dist/runtime/plugin/log.server.d.ts +2 -0
- package/dist/runtime/plugin/log.server.mjs +11 -0
- package/dist/runtime/plugin/shop.d.ts +8 -0
- package/dist/runtime/plugin/shop.mjs +17 -0
- package/dist/runtime/rpc/rpcCall.d.ts +9 -0
- package/dist/runtime/rpc/rpcCall.mjs +16 -0
- package/dist/runtime/server/middleware/bootstrap.d.ts +12 -0
- package/dist/runtime/server/middleware/bootstrap.mjs +188 -0
- package/dist/runtime/server/middleware/bootstrap.test.d.ts +6 -0
- package/dist/runtime/server/middleware/bootstrap.test.mjs +277 -0
- package/dist/runtime/server/middleware/bootstrap.utils.d.ts +7 -0
- package/dist/runtime/server/middleware/bootstrap.utils.mjs +59 -0
- package/dist/runtime/server/middleware/cache.d.ts +12 -0
- package/dist/runtime/server/middleware/cache.mjs +44 -0
- package/dist/runtime/server/middleware/oauth.d.ts +3 -0
- package/dist/runtime/server/middleware/oauth.mjs +123 -0
- package/dist/runtime/server/middleware/redirects.d.ts +2 -0
- package/dist/runtime/server/middleware/redirects.mjs +86 -0
- package/dist/runtime/server/middleware/redirects.test.d.ts +1 -0
- package/dist/runtime/server/middleware/redirects.test.mjs +66 -0
- package/dist/runtime/server/middleware/redirects.utils.d.ts +18 -0
- package/dist/runtime/server/middleware/redirects.utils.mjs +28 -0
- package/dist/runtime/utils/error.d.ts +5 -0
- package/dist/runtime/utils/error.mjs +7 -0
- package/dist/runtime/utils/price.d.ts +11 -0
- package/dist/runtime/utils/price.mjs +32 -0
- package/dist/runtime/utils/price.test.d.ts +1 -0
- package/dist/runtime/utils/price.test.mjs +83 -0
- package/dist/runtime/utils/route.d.ts +2 -0
- package/dist/runtime/utils/route.mjs +3 -0
- package/dist/runtime/utils/route.test.d.ts +1 -0
- package/dist/runtime/utils/route.test.mjs +15 -0
- package/dist/shared/storefront-nuxt.9548700f.d.mts +161 -0
- package/dist/shared/storefront-nuxt.9548700f.d.ts +161 -0
- package/dist/types.d.mts +15 -0
- package/dist/types.d.ts +15 -0
- 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,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
|
+
}
|