@vue-storefront/next 7.0.2-next.2 → 8.0.0-next.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/dist/index.cjs +226 -26
- package/dist/index.d.cts +130 -1
- package/dist/index.d.mts +130 -1
- package/dist/index.mjs +226 -27
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -20,6 +20,231 @@ const createLogger = (options) => {
|
|
|
20
20
|
return injectMetadata(_alokai_connect_logger.LoggerFactory.create(_alokai_connect_logger.LoggerType.ConsolaGcp, options), { alokai: { context: "storefront" } });
|
|
21
21
|
};
|
|
22
22
|
//#endregion
|
|
23
|
+
//#region src/image-optimizer/constants.ts
|
|
24
|
+
/**
|
|
25
|
+
* URL prefix the loader emits and the route folder must use:
|
|
26
|
+
* `app/img-proxy/[host]/[...path]/route.ts`.
|
|
27
|
+
*/
|
|
28
|
+
const IMG_PROXY_PREFIX = "/img-proxy";
|
|
29
|
+
/**
|
|
30
|
+
* Cache-Control applied to successful (2xx) upstream responses unless
|
|
31
|
+
* overridden per host via `cacheControl`.
|
|
32
|
+
*/
|
|
33
|
+
const DEFAULT_CACHE_CONTROL = "public, max-age=3600, s-maxage=86400";
|
|
34
|
+
/**
|
|
35
|
+
* Cache-Control applied to every non-2xx or failed response so errors are
|
|
36
|
+
* never cached by the browser or the CDN.
|
|
37
|
+
*/
|
|
38
|
+
const ERROR_CACHE_CONTROL = "no-store, no-cache, must-revalidate";
|
|
39
|
+
const variants = {
|
|
40
|
+
default: {
|
|
41
|
+
buildUpstreamUrl: (mediaHost, segments) => `${mediaHost}/${joinEncoded(segments)}`,
|
|
42
|
+
encodePath: (pathname) => pathname === "" ? void 0 : pathname
|
|
43
|
+
},
|
|
44
|
+
sapcc: {
|
|
45
|
+
buildUpstreamUrl: (mediaHost, segments) => {
|
|
46
|
+
const [context = "", ...path] = segments;
|
|
47
|
+
return `${mediaHost}/${joinEncoded(path)}?context=${encodeURIComponent(context)}`;
|
|
48
|
+
},
|
|
49
|
+
encodePath: (pathname, searchParams) => {
|
|
50
|
+
var _pathname$split$pop;
|
|
51
|
+
const context = searchParams.get("context");
|
|
52
|
+
if (!context) return;
|
|
53
|
+
const hasExtension = ((_pathname$split$pop = pathname.split("/").pop()) !== null && _pathname$split$pop !== void 0 ? _pathname$split$pop : "").includes(".");
|
|
54
|
+
const separator = pathname.endsWith("/") ? "" : "/";
|
|
55
|
+
const safePathname = hasExtension ? pathname : `${pathname}${separator}image.png`;
|
|
56
|
+
return `/${encodeURIComponent(context)}${safePathname}`;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
function joinEncoded(segments) {
|
|
61
|
+
return segments.map((segment) => encodeURIComponent(segment)).join("/");
|
|
62
|
+
}
|
|
63
|
+
//#endregion
|
|
64
|
+
//#region src/image-optimizer/resolve-hosts.ts
|
|
65
|
+
/**
|
|
66
|
+
* Normalizes the `hosts` config into an ordered list of fully-resolved host
|
|
67
|
+
* configs. Throws on misconfiguration so mistakes surface at module init
|
|
68
|
+
* instead of as silently unoptimized images.
|
|
69
|
+
*
|
|
70
|
+
* @internal
|
|
71
|
+
*/
|
|
72
|
+
function resolveHosts(hosts) {
|
|
73
|
+
return Object.entries(hosts).map(([key, config]) => resolveHost(key, config));
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Derives the conventional env variable name from a host key, e.g.
|
|
77
|
+
* `ct` -> `NEXT_PUBLIC_CT_MEDIA_HOST`, `my-cms` -> `NEXT_PUBLIC_MY_CMS_MEDIA_HOST`.
|
|
78
|
+
*
|
|
79
|
+
* @internal
|
|
80
|
+
*/
|
|
81
|
+
function deriveMediaHostEnvName(hostKey) {
|
|
82
|
+
return `NEXT_PUBLIC_${hostKey.replaceAll("-", "_").toUpperCase()}_MEDIA_HOST`;
|
|
83
|
+
}
|
|
84
|
+
const HOST_KEY_PATTERN = /^(?=.*[a-z])[a-z0-9-]+$/;
|
|
85
|
+
const ENV_NAME_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
86
|
+
function resolveHost(key, config) {
|
|
87
|
+
var _config$mediaHostEnvN, _config$variant, _config$buildUpstream, _config$cacheControl, _config$encodePath;
|
|
88
|
+
if (!HOST_KEY_PATTERN.test(key)) throw new Error(`Invalid image optimizer host key "${key}". Use lowercase letters, digits and hyphens only - the key becomes a URL segment and an env variable name fragment.`);
|
|
89
|
+
const mediaHostEnvName = (_config$mediaHostEnvN = config.mediaHostEnvName) !== null && _config$mediaHostEnvN !== void 0 ? _config$mediaHostEnvN : deriveMediaHostEnvName(key);
|
|
90
|
+
if (!ENV_NAME_PATTERN.test(mediaHostEnvName)) throw new Error(`Invalid mediaHostEnvName "${mediaHostEnvName}" for host "${key}". Pass the NAME of the env variable (e.g. "NEXT_PUBLIC_CT_MEDIA_HOST"), not its value.`);
|
|
91
|
+
const variant = variants[(_config$variant = config.variant) !== null && _config$variant !== void 0 ? _config$variant : "default"];
|
|
92
|
+
return {
|
|
93
|
+
buildUpstreamUrl: (_config$buildUpstream = config.buildUpstreamUrl) !== null && _config$buildUpstream !== void 0 ? _config$buildUpstream : variant.buildUpstreamUrl,
|
|
94
|
+
cacheControl: (_config$cacheControl = config.cacheControl) !== null && _config$cacheControl !== void 0 ? _config$cacheControl : DEFAULT_CACHE_CONTROL,
|
|
95
|
+
encodePath: (_config$encodePath = config.encodePath) !== null && _config$encodePath !== void 0 ? _config$encodePath : variant.encodePath,
|
|
96
|
+
key,
|
|
97
|
+
loader: config.loader,
|
|
98
|
+
mediaHostEnvName
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
//#endregion
|
|
102
|
+
//#region \0@oxc-project+runtime@0.115.0/helpers/asyncToGenerator.js
|
|
103
|
+
function asyncGeneratorStep(n, t, e, r, o, a, c) {
|
|
104
|
+
try {
|
|
105
|
+
var i = n[a](c), u = i.value;
|
|
106
|
+
} catch (n) {
|
|
107
|
+
e(n);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
i.done ? t(u) : Promise.resolve(u).then(r, o);
|
|
111
|
+
}
|
|
112
|
+
function _asyncToGenerator(n) {
|
|
113
|
+
return function() {
|
|
114
|
+
var t = this, e = arguments;
|
|
115
|
+
return new Promise(function(r, o) {
|
|
116
|
+
var a = n.apply(t, e);
|
|
117
|
+
function _next(n) {
|
|
118
|
+
asyncGeneratorStep(a, r, o, _next, _throw, "next", n);
|
|
119
|
+
}
|
|
120
|
+
function _throw(n) {
|
|
121
|
+
asyncGeneratorStep(a, r, o, _next, _throw, "throw", n);
|
|
122
|
+
}
|
|
123
|
+
_next(void 0);
|
|
124
|
+
});
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
//#endregion
|
|
128
|
+
//#region src/image-optimizer/create-image-optimizer.ts
|
|
129
|
+
/**
|
|
130
|
+
* Creates a Next.js custom image `loader` and a proxy route-handler `GET`
|
|
131
|
+
* that together let an image CDN in front of the storefront (the Alokai
|
|
132
|
+
* Image Optimizer)
|
|
133
|
+
* optimize media-host images.
|
|
134
|
+
*
|
|
135
|
+
* The loader rewrites `src` values matching a configured media host to
|
|
136
|
+
* `/img-proxy/{key}/{path}?width=&quality=&format=auto`; the route handler
|
|
137
|
+
* proxies that path back to the media host and streams the body so the CDN
|
|
138
|
+
* can transform it on the way to the browser. Non-matching `src` values are
|
|
139
|
+
* returned unchanged.
|
|
140
|
+
*
|
|
141
|
+
* @example
|
|
142
|
+
* ```ts
|
|
143
|
+
* // config/image-optimizer.ts - the single place the config exists; the
|
|
144
|
+
* // default export lets `images.loaderFile` point straight at this file
|
|
145
|
+
* import { createImageOptimizer } from "@vue-storefront/next";
|
|
146
|
+
*
|
|
147
|
+
* export const { GET, loader } = createImageOptimizer({ hosts: { ct: {} } });
|
|
148
|
+
*
|
|
149
|
+
* export default loader;
|
|
150
|
+
*
|
|
151
|
+
* // app/img-proxy/[host]/[...path]/route.ts
|
|
152
|
+
* export { GET } from "@/config/image-optimizer";
|
|
153
|
+
* ```
|
|
154
|
+
*/
|
|
155
|
+
function createImageOptimizer(config) {
|
|
156
|
+
if (Object.keys(config.hosts).length === 0) throw new Error([
|
|
157
|
+
"createImageOptimizer requires at least one host in \"hosts\".",
|
|
158
|
+
"Add the media hosts whose images the loader should optimize, e.g.:",
|
|
159
|
+
"",
|
|
160
|
+
" createImageOptimizer({",
|
|
161
|
+
" hosts: {",
|
|
162
|
+
" ct: {},",
|
|
163
|
+
" sapcc: { variant: \"sapcc\" },",
|
|
164
|
+
" },",
|
|
165
|
+
" });",
|
|
166
|
+
"",
|
|
167
|
+
"Each key becomes a URL segment and derives the env variable holding",
|
|
168
|
+
"the media host URL, e.g. \"ct\" reads NEXT_PUBLIC_CT_MEDIA_HOST."
|
|
169
|
+
].join("\n"));
|
|
170
|
+
const hosts = resolveHosts(config.hosts);
|
|
171
|
+
function loader({ quality, src, width }) {
|
|
172
|
+
for (const host of hosts) {
|
|
173
|
+
const mediaHost = require_env.env(host.mediaHostEnvName);
|
|
174
|
+
if (!mediaHost) {
|
|
175
|
+
getLogger().warning(`${host.mediaHostEnvName} is not defined, skipping image optimization for host "${host.key}".`);
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
const normalizedHost = mediaHost.replace(/\/$/, "");
|
|
179
|
+
if (!matchesMediaHost(src, normalizedHost)) continue;
|
|
180
|
+
if (host.loader) return host.loader({
|
|
181
|
+
quality,
|
|
182
|
+
src,
|
|
183
|
+
width
|
|
184
|
+
});
|
|
185
|
+
const localPath = src.slice(normalizedHost.length);
|
|
186
|
+
const queryIndex = localPath.indexOf("?");
|
|
187
|
+
const pathname = queryIndex === -1 ? localPath : localPath.slice(0, queryIndex);
|
|
188
|
+
const queryString = queryIndex === -1 ? "" : localPath.slice(queryIndex + 1);
|
|
189
|
+
const proxyPath = host.encodePath(pathname, new URLSearchParams(queryString));
|
|
190
|
+
if (proxyPath === void 0) return src;
|
|
191
|
+
return `${IMG_PROXY_PREFIX}/${host.key}${proxyPath}?width=${width}&quality=${quality || 85}&format=auto`;
|
|
192
|
+
}
|
|
193
|
+
return src;
|
|
194
|
+
}
|
|
195
|
+
function GET(_x, _x2) {
|
|
196
|
+
return _GET.apply(this, arguments);
|
|
197
|
+
}
|
|
198
|
+
function _GET() {
|
|
199
|
+
_GET = _asyncToGenerator(function* (_request, { params }) {
|
|
200
|
+
var _env, _upstream$headers$get;
|
|
201
|
+
const { host: hostKey, path } = yield params;
|
|
202
|
+
const host = hosts.find((entry) => entry.key === hostKey);
|
|
203
|
+
if (!host) return errorResponse(`Unknown image proxy host "${hostKey}"`, 404);
|
|
204
|
+
if (path.length === 0 || path.some(isUnsafeSegment)) return errorResponse("Invalid image path", 400);
|
|
205
|
+
const mediaHost = (_env = require_env.env(host.mediaHostEnvName)) === null || _env === void 0 ? void 0 : _env.replace(/\/$/, "");
|
|
206
|
+
if (!mediaHost) return errorResponse(`${host.mediaHostEnvName} is not defined`, 500);
|
|
207
|
+
let upstream;
|
|
208
|
+
try {
|
|
209
|
+
upstream = yield fetch(host.buildUpstreamUrl(mediaHost, path));
|
|
210
|
+
} catch (error) {
|
|
211
|
+
getLogger().error(`Failed to fetch upstream image for host "${hostKey}": ${String(error)}`);
|
|
212
|
+
return errorResponse("Failed to fetch upstream image", 502);
|
|
213
|
+
}
|
|
214
|
+
return new Response(upstream.body, {
|
|
215
|
+
headers: {
|
|
216
|
+
"cache-control": upstream.ok ? host.cacheControl : ERROR_CACHE_CONTROL,
|
|
217
|
+
"content-type": (_upstream$headers$get = upstream.headers.get("content-type")) !== null && _upstream$headers$get !== void 0 ? _upstream$headers$get : "application/octet-stream"
|
|
218
|
+
},
|
|
219
|
+
status: upstream.status
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
return _GET.apply(this, arguments);
|
|
223
|
+
}
|
|
224
|
+
return {
|
|
225
|
+
GET,
|
|
226
|
+
loader
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
let logger;
|
|
230
|
+
function getLogger() {
|
|
231
|
+
var _logger;
|
|
232
|
+
(_logger = logger) !== null && _logger !== void 0 || (logger = createLogger());
|
|
233
|
+
return logger;
|
|
234
|
+
}
|
|
235
|
+
function matchesMediaHost(src, normalizedHost) {
|
|
236
|
+
return src === normalizedHost || src.startsWith(`${normalizedHost}/`) || src.startsWith(`${normalizedHost}?`);
|
|
237
|
+
}
|
|
238
|
+
function isUnsafeSegment(segment) {
|
|
239
|
+
return segment === "" || segment === "." || segment === "..";
|
|
240
|
+
}
|
|
241
|
+
function errorResponse(message, status) {
|
|
242
|
+
return new Response(message, {
|
|
243
|
+
headers: { "cache-control": ERROR_CACHE_CONTROL },
|
|
244
|
+
status
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
//#endregion
|
|
23
248
|
//#region src/middleware.ts
|
|
24
249
|
/**
|
|
25
250
|
* Creates an Alokai middleware wrapper that adds pathname information to request headers.
|
|
@@ -64,32 +289,6 @@ const defaultMethodsRequestConfig = {
|
|
|
64
289
|
} }
|
|
65
290
|
};
|
|
66
291
|
//#endregion
|
|
67
|
-
//#region \0@oxc-project+runtime@0.115.0/helpers/asyncToGenerator.js
|
|
68
|
-
function asyncGeneratorStep(n, t, e, r, o, a, c) {
|
|
69
|
-
try {
|
|
70
|
-
var i = n[a](c), u = i.value;
|
|
71
|
-
} catch (n) {
|
|
72
|
-
e(n);
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
i.done ? t(u) : Promise.resolve(u).then(r, o);
|
|
76
|
-
}
|
|
77
|
-
function _asyncToGenerator(n) {
|
|
78
|
-
return function() {
|
|
79
|
-
var t = this, e = arguments;
|
|
80
|
-
return new Promise(function(r, o) {
|
|
81
|
-
var a = n.apply(t, e);
|
|
82
|
-
function _next(n) {
|
|
83
|
-
asyncGeneratorStep(a, r, o, _next, _throw, "next", n);
|
|
84
|
-
}
|
|
85
|
-
function _throw(n) {
|
|
86
|
-
asyncGeneratorStep(a, r, o, _next, _throw, "throw", n);
|
|
87
|
-
}
|
|
88
|
-
_next(void 0);
|
|
89
|
-
});
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
//#endregion
|
|
93
292
|
//#region src/sdk/helpers/resolveDynamicContext.ts
|
|
94
293
|
const BLACKLISTED_HEADERS = new Set(["host"]);
|
|
95
294
|
function isAppRouterHeaders(headers) {
|
|
@@ -237,6 +436,7 @@ function buildModules(modules) {
|
|
|
237
436
|
}
|
|
238
437
|
//#endregion
|
|
239
438
|
exports.createAlokaiMiddleware = createAlokaiMiddleware;
|
|
439
|
+
exports.createImageOptimizer = createImageOptimizer;
|
|
240
440
|
exports.createLogger = createLogger;
|
|
241
441
|
exports.createSdk = createSdk;
|
|
242
442
|
Object.defineProperty(exports, "defineGetConfigSwitcherHeader", {
|
package/dist/index.d.cts
CHANGED
|
@@ -1,8 +1,137 @@
|
|
|
1
1
|
import { a as CreateSdkReturn, i as CreateSdkOptions, n as Config, o as InjectedContext, t as env } from "./index-w0GzdXwC.cjs";
|
|
2
2
|
import { buildModule, defineGetConfigSwitcherHeader } from "@alokai/connect/sdk";
|
|
3
|
+
import { ImageLoaderProps } from "next/image";
|
|
3
4
|
import * as _alokai_connect_logger0 from "@alokai/connect/logger";
|
|
4
5
|
import { NextRequest, NextResponse } from "next/server";
|
|
5
6
|
|
|
7
|
+
//#region src/image-optimizer/types.d.ts
|
|
8
|
+
/**
|
|
9
|
+
* Next.js custom image loader produced by `createImageOptimizer`. Pass it as
|
|
10
|
+
* the default export of the file referenced by `images.loaderFile`.
|
|
11
|
+
*/
|
|
12
|
+
type ImageOptimizerLoader = (props: ImageLoaderProps) => string;
|
|
13
|
+
/**
|
|
14
|
+
* Route handler produced by `createImageOptimizer`. Re-export it as `GET`
|
|
15
|
+
* from `app/img-proxy/[host]/[...path]/route.ts`.
|
|
16
|
+
*/
|
|
17
|
+
type ImageOptimizerRouteHandler = (request: Request, context: ImageOptimizerRouteContext) => Promise<Response>;
|
|
18
|
+
interface ImageOptimizer {
|
|
19
|
+
GET: ImageOptimizerRouteHandler;
|
|
20
|
+
loader: ImageOptimizerLoader;
|
|
21
|
+
}
|
|
22
|
+
interface CreateImageOptimizerConfig {
|
|
23
|
+
/**
|
|
24
|
+
* Media hosts whose images should be optimized, keyed by host key. An
|
|
25
|
+
* empty object means all defaults. The key becomes the `[host]` URL
|
|
26
|
+
* segment and derives the default env variable name:
|
|
27
|
+
* `NEXT_PUBLIC_{KEY}_MEDIA_HOST`.
|
|
28
|
+
*/
|
|
29
|
+
hosts: ImageOptimizerHosts;
|
|
30
|
+
}
|
|
31
|
+
type ImageOptimizerHosts = Record<string, ImageOptimizerHostConfig>;
|
|
32
|
+
type ImageOptimizerHostConfig = ImageOptimizerDelegatedHostConfig | ImageOptimizerProxiedHostConfig;
|
|
33
|
+
/**
|
|
34
|
+
* Host with its own image CDN (e.g. Contentful): matching `src` values are
|
|
35
|
+
* delegated to the given loader and never proxied, so the proxy-side options
|
|
36
|
+
* do not apply.
|
|
37
|
+
*/
|
|
38
|
+
interface ImageOptimizerDelegatedHostConfig extends ImageOptimizerHostBaseConfig {
|
|
39
|
+
buildUpstreamUrl?: never;
|
|
40
|
+
cacheControl?: never;
|
|
41
|
+
encodePath?: never;
|
|
42
|
+
/**
|
|
43
|
+
* Rewrites a matching `src` directly to the host's own CDN. Same signature
|
|
44
|
+
* as a Next.js image loader, so existing loader implementations plug in.
|
|
45
|
+
*/
|
|
46
|
+
loader: ImageOptimizerLoader;
|
|
47
|
+
variant?: never;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Host proxied through `/img-proxy/{key}/...` so the image CDN in front of
|
|
51
|
+
* the storefront (Alokai Image Optimizer) can transform the response.
|
|
52
|
+
*/
|
|
53
|
+
interface ImageOptimizerProxiedHostConfig extends ImageOptimizerHostBaseConfig {
|
|
54
|
+
/** Server-side hook overriding the variant's upstream URL reconstruction. */
|
|
55
|
+
buildUpstreamUrl?: BuildUpstreamUrlHook;
|
|
56
|
+
/**
|
|
57
|
+
* Cache-Control for successful (2xx) upstream responses.
|
|
58
|
+
*
|
|
59
|
+
* @default "public, max-age=3600, s-maxage=86400"
|
|
60
|
+
*/
|
|
61
|
+
cacheControl?: string;
|
|
62
|
+
/** Client-side hook overriding the variant's path encoding. */
|
|
63
|
+
encodePath?: EncodePathHook;
|
|
64
|
+
loader?: never;
|
|
65
|
+
/**
|
|
66
|
+
* Built-in URL scheme. `sapcc` encodes the `?context=` query parameter as
|
|
67
|
+
* a path segment and appends a synthetic filename when the path has none.
|
|
68
|
+
* Ignored for a side when the corresponding hook is provided.
|
|
69
|
+
*
|
|
70
|
+
* @default "default"
|
|
71
|
+
*/
|
|
72
|
+
variant?: ImageOptimizerVariant;
|
|
73
|
+
}
|
|
74
|
+
interface ImageOptimizerHostBaseConfig {
|
|
75
|
+
/**
|
|
76
|
+
* NAME of the env variable holding the media host URL (not the URL
|
|
77
|
+
* itself).
|
|
78
|
+
*
|
|
79
|
+
* @default `NEXT_PUBLIC_{KEY}_MEDIA_HOST` derived from the host key
|
|
80
|
+
*/
|
|
81
|
+
mediaHostEnvName?: string;
|
|
82
|
+
}
|
|
83
|
+
type ImageOptimizerVariant = "default" | "sapcc";
|
|
84
|
+
/**
|
|
85
|
+
* Builds the path emitted after `/img-proxy/{key}` on the loader (client)
|
|
86
|
+
* side. Receives the pathname relative to the media host (with leading
|
|
87
|
+
* slash, without query string) and the parsed query string of the original
|
|
88
|
+
* `src`. Return the proxy path (starting with `/`), or `undefined` to skip
|
|
89
|
+
* proxying and return the original `src` unchanged.
|
|
90
|
+
*/
|
|
91
|
+
type EncodePathHook = (pathname: string, searchParams: URLSearchParams) => string | undefined;
|
|
92
|
+
/**
|
|
93
|
+
* Rebuilds the upstream URL on the route-handler (server) side. Receives the
|
|
94
|
+
* media host (without trailing slash) and the decoded `[...path]` segments
|
|
95
|
+
* from the route params. Must return an absolute URL.
|
|
96
|
+
*/
|
|
97
|
+
type BuildUpstreamUrlHook = (mediaHost: string, segments: string[]) => string;
|
|
98
|
+
interface ImageOptimizerRouteContext {
|
|
99
|
+
params: Promise<ImageOptimizerRouteParams>;
|
|
100
|
+
}
|
|
101
|
+
interface ImageOptimizerRouteParams {
|
|
102
|
+
host: string;
|
|
103
|
+
path: string[];
|
|
104
|
+
}
|
|
105
|
+
//#endregion
|
|
106
|
+
//#region src/image-optimizer/create-image-optimizer.d.ts
|
|
107
|
+
/**
|
|
108
|
+
* Creates a Next.js custom image `loader` and a proxy route-handler `GET`
|
|
109
|
+
* that together let an image CDN in front of the storefront (the Alokai
|
|
110
|
+
* Image Optimizer)
|
|
111
|
+
* optimize media-host images.
|
|
112
|
+
*
|
|
113
|
+
* The loader rewrites `src` values matching a configured media host to
|
|
114
|
+
* `/img-proxy/{key}/{path}?width=&quality=&format=auto`; the route handler
|
|
115
|
+
* proxies that path back to the media host and streams the body so the CDN
|
|
116
|
+
* can transform it on the way to the browser. Non-matching `src` values are
|
|
117
|
+
* returned unchanged.
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* ```ts
|
|
121
|
+
* // config/image-optimizer.ts - the single place the config exists; the
|
|
122
|
+
* // default export lets `images.loaderFile` point straight at this file
|
|
123
|
+
* import { createImageOptimizer } from "@vue-storefront/next";
|
|
124
|
+
*
|
|
125
|
+
* export const { GET, loader } = createImageOptimizer({ hosts: { ct: {} } });
|
|
126
|
+
*
|
|
127
|
+
* export default loader;
|
|
128
|
+
*
|
|
129
|
+
* // app/img-proxy/[host]/[...path]/route.ts
|
|
130
|
+
* export { GET } from "@/config/image-optimizer";
|
|
131
|
+
* ```
|
|
132
|
+
*/
|
|
133
|
+
declare function createImageOptimizer(config: CreateImageOptimizerConfig): ImageOptimizer;
|
|
134
|
+
//#endregion
|
|
6
135
|
//#region src/logger/types.d.ts
|
|
7
136
|
type LogVerbosity = "alert" | "critical" | "debug" | "emergency" | "error" | "info" | "notice" | "warning";
|
|
8
137
|
type LoggerOptions = Partial<{
|
|
@@ -176,4 +305,4 @@ type DefineSdkModule = (context: InjectedContext) => ReturnType<typeof buildModu
|
|
|
176
305
|
*/
|
|
177
306
|
declare function defineSdkModule<TModuleDefinition extends DefineSdkModule>(moduleDefinition: TModuleDefinition): TModuleDefinition;
|
|
178
307
|
//#endregion
|
|
179
|
-
export { type CreateSdkOptions, createAlokaiMiddleware, createLogger, createSdk, defineGetConfigSwitcherHeader, defineSdkConfig, defineSdkModule, env, resolveSdkOptions };
|
|
308
|
+
export { BuildUpstreamUrlHook, CreateImageOptimizerConfig, type CreateSdkOptions, EncodePathHook, ImageOptimizer, ImageOptimizerDelegatedHostConfig, ImageOptimizerHostConfig, ImageOptimizerHosts, ImageOptimizerLoader, ImageOptimizerProxiedHostConfig, ImageOptimizerRouteContext, ImageOptimizerRouteHandler, ImageOptimizerRouteParams, ImageOptimizerVariant, createAlokaiMiddleware, createImageOptimizer, createLogger, createSdk, defineGetConfigSwitcherHeader, defineSdkConfig, defineSdkModule, env, resolveSdkOptions };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,8 +1,137 @@
|
|
|
1
1
|
import { a as CreateSdkReturn, i as CreateSdkOptions, n as Config, o as InjectedContext, t as env } from "./index-BPhD221E.mjs";
|
|
2
2
|
import * as _alokai_connect_logger0 from "@alokai/connect/logger";
|
|
3
3
|
import { buildModule, defineGetConfigSwitcherHeader } from "@alokai/connect/sdk";
|
|
4
|
+
import { ImageLoaderProps } from "next/image";
|
|
4
5
|
import { NextRequest, NextResponse } from "next/server";
|
|
5
6
|
|
|
7
|
+
//#region src/image-optimizer/types.d.ts
|
|
8
|
+
/**
|
|
9
|
+
* Next.js custom image loader produced by `createImageOptimizer`. Pass it as
|
|
10
|
+
* the default export of the file referenced by `images.loaderFile`.
|
|
11
|
+
*/
|
|
12
|
+
type ImageOptimizerLoader = (props: ImageLoaderProps) => string;
|
|
13
|
+
/**
|
|
14
|
+
* Route handler produced by `createImageOptimizer`. Re-export it as `GET`
|
|
15
|
+
* from `app/img-proxy/[host]/[...path]/route.ts`.
|
|
16
|
+
*/
|
|
17
|
+
type ImageOptimizerRouteHandler = (request: Request, context: ImageOptimizerRouteContext) => Promise<Response>;
|
|
18
|
+
interface ImageOptimizer {
|
|
19
|
+
GET: ImageOptimizerRouteHandler;
|
|
20
|
+
loader: ImageOptimizerLoader;
|
|
21
|
+
}
|
|
22
|
+
interface CreateImageOptimizerConfig {
|
|
23
|
+
/**
|
|
24
|
+
* Media hosts whose images should be optimized, keyed by host key. An
|
|
25
|
+
* empty object means all defaults. The key becomes the `[host]` URL
|
|
26
|
+
* segment and derives the default env variable name:
|
|
27
|
+
* `NEXT_PUBLIC_{KEY}_MEDIA_HOST`.
|
|
28
|
+
*/
|
|
29
|
+
hosts: ImageOptimizerHosts;
|
|
30
|
+
}
|
|
31
|
+
type ImageOptimizerHosts = Record<string, ImageOptimizerHostConfig>;
|
|
32
|
+
type ImageOptimizerHostConfig = ImageOptimizerDelegatedHostConfig | ImageOptimizerProxiedHostConfig;
|
|
33
|
+
/**
|
|
34
|
+
* Host with its own image CDN (e.g. Contentful): matching `src` values are
|
|
35
|
+
* delegated to the given loader and never proxied, so the proxy-side options
|
|
36
|
+
* do not apply.
|
|
37
|
+
*/
|
|
38
|
+
interface ImageOptimizerDelegatedHostConfig extends ImageOptimizerHostBaseConfig {
|
|
39
|
+
buildUpstreamUrl?: never;
|
|
40
|
+
cacheControl?: never;
|
|
41
|
+
encodePath?: never;
|
|
42
|
+
/**
|
|
43
|
+
* Rewrites a matching `src` directly to the host's own CDN. Same signature
|
|
44
|
+
* as a Next.js image loader, so existing loader implementations plug in.
|
|
45
|
+
*/
|
|
46
|
+
loader: ImageOptimizerLoader;
|
|
47
|
+
variant?: never;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Host proxied through `/img-proxy/{key}/...` so the image CDN in front of
|
|
51
|
+
* the storefront (Alokai Image Optimizer) can transform the response.
|
|
52
|
+
*/
|
|
53
|
+
interface ImageOptimizerProxiedHostConfig extends ImageOptimizerHostBaseConfig {
|
|
54
|
+
/** Server-side hook overriding the variant's upstream URL reconstruction. */
|
|
55
|
+
buildUpstreamUrl?: BuildUpstreamUrlHook;
|
|
56
|
+
/**
|
|
57
|
+
* Cache-Control for successful (2xx) upstream responses.
|
|
58
|
+
*
|
|
59
|
+
* @default "public, max-age=3600, s-maxage=86400"
|
|
60
|
+
*/
|
|
61
|
+
cacheControl?: string;
|
|
62
|
+
/** Client-side hook overriding the variant's path encoding. */
|
|
63
|
+
encodePath?: EncodePathHook;
|
|
64
|
+
loader?: never;
|
|
65
|
+
/**
|
|
66
|
+
* Built-in URL scheme. `sapcc` encodes the `?context=` query parameter as
|
|
67
|
+
* a path segment and appends a synthetic filename when the path has none.
|
|
68
|
+
* Ignored for a side when the corresponding hook is provided.
|
|
69
|
+
*
|
|
70
|
+
* @default "default"
|
|
71
|
+
*/
|
|
72
|
+
variant?: ImageOptimizerVariant;
|
|
73
|
+
}
|
|
74
|
+
interface ImageOptimizerHostBaseConfig {
|
|
75
|
+
/**
|
|
76
|
+
* NAME of the env variable holding the media host URL (not the URL
|
|
77
|
+
* itself).
|
|
78
|
+
*
|
|
79
|
+
* @default `NEXT_PUBLIC_{KEY}_MEDIA_HOST` derived from the host key
|
|
80
|
+
*/
|
|
81
|
+
mediaHostEnvName?: string;
|
|
82
|
+
}
|
|
83
|
+
type ImageOptimizerVariant = "default" | "sapcc";
|
|
84
|
+
/**
|
|
85
|
+
* Builds the path emitted after `/img-proxy/{key}` on the loader (client)
|
|
86
|
+
* side. Receives the pathname relative to the media host (with leading
|
|
87
|
+
* slash, without query string) and the parsed query string of the original
|
|
88
|
+
* `src`. Return the proxy path (starting with `/`), or `undefined` to skip
|
|
89
|
+
* proxying and return the original `src` unchanged.
|
|
90
|
+
*/
|
|
91
|
+
type EncodePathHook = (pathname: string, searchParams: URLSearchParams) => string | undefined;
|
|
92
|
+
/**
|
|
93
|
+
* Rebuilds the upstream URL on the route-handler (server) side. Receives the
|
|
94
|
+
* media host (without trailing slash) and the decoded `[...path]` segments
|
|
95
|
+
* from the route params. Must return an absolute URL.
|
|
96
|
+
*/
|
|
97
|
+
type BuildUpstreamUrlHook = (mediaHost: string, segments: string[]) => string;
|
|
98
|
+
interface ImageOptimizerRouteContext {
|
|
99
|
+
params: Promise<ImageOptimizerRouteParams>;
|
|
100
|
+
}
|
|
101
|
+
interface ImageOptimizerRouteParams {
|
|
102
|
+
host: string;
|
|
103
|
+
path: string[];
|
|
104
|
+
}
|
|
105
|
+
//#endregion
|
|
106
|
+
//#region src/image-optimizer/create-image-optimizer.d.ts
|
|
107
|
+
/**
|
|
108
|
+
* Creates a Next.js custom image `loader` and a proxy route-handler `GET`
|
|
109
|
+
* that together let an image CDN in front of the storefront (the Alokai
|
|
110
|
+
* Image Optimizer)
|
|
111
|
+
* optimize media-host images.
|
|
112
|
+
*
|
|
113
|
+
* The loader rewrites `src` values matching a configured media host to
|
|
114
|
+
* `/img-proxy/{key}/{path}?width=&quality=&format=auto`; the route handler
|
|
115
|
+
* proxies that path back to the media host and streams the body so the CDN
|
|
116
|
+
* can transform it on the way to the browser. Non-matching `src` values are
|
|
117
|
+
* returned unchanged.
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* ```ts
|
|
121
|
+
* // config/image-optimizer.ts - the single place the config exists; the
|
|
122
|
+
* // default export lets `images.loaderFile` point straight at this file
|
|
123
|
+
* import { createImageOptimizer } from "@vue-storefront/next";
|
|
124
|
+
*
|
|
125
|
+
* export const { GET, loader } = createImageOptimizer({ hosts: { ct: {} } });
|
|
126
|
+
*
|
|
127
|
+
* export default loader;
|
|
128
|
+
*
|
|
129
|
+
* // app/img-proxy/[host]/[...path]/route.ts
|
|
130
|
+
* export { GET } from "@/config/image-optimizer";
|
|
131
|
+
* ```
|
|
132
|
+
*/
|
|
133
|
+
declare function createImageOptimizer(config: CreateImageOptimizerConfig): ImageOptimizer;
|
|
134
|
+
//#endregion
|
|
6
135
|
//#region src/logger/types.d.ts
|
|
7
136
|
type LogVerbosity = "alert" | "critical" | "debug" | "emergency" | "error" | "info" | "notice" | "warning";
|
|
8
137
|
type LoggerOptions = Partial<{
|
|
@@ -176,4 +305,4 @@ type DefineSdkModule = (context: InjectedContext) => ReturnType<typeof buildModu
|
|
|
176
305
|
*/
|
|
177
306
|
declare function defineSdkModule<TModuleDefinition extends DefineSdkModule>(moduleDefinition: TModuleDefinition): TModuleDefinition;
|
|
178
307
|
//#endregion
|
|
179
|
-
export { type CreateSdkOptions, createAlokaiMiddleware, createLogger, createSdk, defineGetConfigSwitcherHeader, defineSdkConfig, defineSdkModule, env, resolveSdkOptions };
|
|
308
|
+
export { BuildUpstreamUrlHook, CreateImageOptimizerConfig, type CreateSdkOptions, EncodePathHook, ImageOptimizer, ImageOptimizerDelegatedHostConfig, ImageOptimizerHostConfig, ImageOptimizerHosts, ImageOptimizerLoader, ImageOptimizerProxiedHostConfig, ImageOptimizerRouteContext, ImageOptimizerRouteHandler, ImageOptimizerRouteParams, ImageOptimizerVariant, createAlokaiMiddleware, createImageOptimizer, createLogger, createSdk, defineGetConfigSwitcherHeader, defineSdkConfig, defineSdkModule, env, resolveSdkOptions };
|
package/dist/index.mjs
CHANGED
|
@@ -18,6 +18,231 @@ const createLogger = (options) => {
|
|
|
18
18
|
return injectMetadata(LoggerFactory.create(LoggerType.ConsolaGcp, options), { alokai: { context: "storefront" } });
|
|
19
19
|
};
|
|
20
20
|
//#endregion
|
|
21
|
+
//#region src/image-optimizer/constants.ts
|
|
22
|
+
/**
|
|
23
|
+
* URL prefix the loader emits and the route folder must use:
|
|
24
|
+
* `app/img-proxy/[host]/[...path]/route.ts`.
|
|
25
|
+
*/
|
|
26
|
+
const IMG_PROXY_PREFIX = "/img-proxy";
|
|
27
|
+
/**
|
|
28
|
+
* Cache-Control applied to successful (2xx) upstream responses unless
|
|
29
|
+
* overridden per host via `cacheControl`.
|
|
30
|
+
*/
|
|
31
|
+
const DEFAULT_CACHE_CONTROL = "public, max-age=3600, s-maxage=86400";
|
|
32
|
+
/**
|
|
33
|
+
* Cache-Control applied to every non-2xx or failed response so errors are
|
|
34
|
+
* never cached by the browser or the CDN.
|
|
35
|
+
*/
|
|
36
|
+
const ERROR_CACHE_CONTROL = "no-store, no-cache, must-revalidate";
|
|
37
|
+
const variants = {
|
|
38
|
+
default: {
|
|
39
|
+
buildUpstreamUrl: (mediaHost, segments) => `${mediaHost}/${joinEncoded(segments)}`,
|
|
40
|
+
encodePath: (pathname) => pathname === "" ? void 0 : pathname
|
|
41
|
+
},
|
|
42
|
+
sapcc: {
|
|
43
|
+
buildUpstreamUrl: (mediaHost, segments) => {
|
|
44
|
+
const [context = "", ...path] = segments;
|
|
45
|
+
return `${mediaHost}/${joinEncoded(path)}?context=${encodeURIComponent(context)}`;
|
|
46
|
+
},
|
|
47
|
+
encodePath: (pathname, searchParams) => {
|
|
48
|
+
var _pathname$split$pop;
|
|
49
|
+
const context = searchParams.get("context");
|
|
50
|
+
if (!context) return;
|
|
51
|
+
const hasExtension = ((_pathname$split$pop = pathname.split("/").pop()) !== null && _pathname$split$pop !== void 0 ? _pathname$split$pop : "").includes(".");
|
|
52
|
+
const separator = pathname.endsWith("/") ? "" : "/";
|
|
53
|
+
const safePathname = hasExtension ? pathname : `${pathname}${separator}image.png`;
|
|
54
|
+
return `/${encodeURIComponent(context)}${safePathname}`;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
function joinEncoded(segments) {
|
|
59
|
+
return segments.map((segment) => encodeURIComponent(segment)).join("/");
|
|
60
|
+
}
|
|
61
|
+
//#endregion
|
|
62
|
+
//#region src/image-optimizer/resolve-hosts.ts
|
|
63
|
+
/**
|
|
64
|
+
* Normalizes the `hosts` config into an ordered list of fully-resolved host
|
|
65
|
+
* configs. Throws on misconfiguration so mistakes surface at module init
|
|
66
|
+
* instead of as silently unoptimized images.
|
|
67
|
+
*
|
|
68
|
+
* @internal
|
|
69
|
+
*/
|
|
70
|
+
function resolveHosts(hosts) {
|
|
71
|
+
return Object.entries(hosts).map(([key, config]) => resolveHost(key, config));
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Derives the conventional env variable name from a host key, e.g.
|
|
75
|
+
* `ct` -> `NEXT_PUBLIC_CT_MEDIA_HOST`, `my-cms` -> `NEXT_PUBLIC_MY_CMS_MEDIA_HOST`.
|
|
76
|
+
*
|
|
77
|
+
* @internal
|
|
78
|
+
*/
|
|
79
|
+
function deriveMediaHostEnvName(hostKey) {
|
|
80
|
+
return `NEXT_PUBLIC_${hostKey.replaceAll("-", "_").toUpperCase()}_MEDIA_HOST`;
|
|
81
|
+
}
|
|
82
|
+
const HOST_KEY_PATTERN = /^(?=.*[a-z])[a-z0-9-]+$/;
|
|
83
|
+
const ENV_NAME_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
84
|
+
function resolveHost(key, config) {
|
|
85
|
+
var _config$mediaHostEnvN, _config$variant, _config$buildUpstream, _config$cacheControl, _config$encodePath;
|
|
86
|
+
if (!HOST_KEY_PATTERN.test(key)) throw new Error(`Invalid image optimizer host key "${key}". Use lowercase letters, digits and hyphens only - the key becomes a URL segment and an env variable name fragment.`);
|
|
87
|
+
const mediaHostEnvName = (_config$mediaHostEnvN = config.mediaHostEnvName) !== null && _config$mediaHostEnvN !== void 0 ? _config$mediaHostEnvN : deriveMediaHostEnvName(key);
|
|
88
|
+
if (!ENV_NAME_PATTERN.test(mediaHostEnvName)) throw new Error(`Invalid mediaHostEnvName "${mediaHostEnvName}" for host "${key}". Pass the NAME of the env variable (e.g. "NEXT_PUBLIC_CT_MEDIA_HOST"), not its value.`);
|
|
89
|
+
const variant = variants[(_config$variant = config.variant) !== null && _config$variant !== void 0 ? _config$variant : "default"];
|
|
90
|
+
return {
|
|
91
|
+
buildUpstreamUrl: (_config$buildUpstream = config.buildUpstreamUrl) !== null && _config$buildUpstream !== void 0 ? _config$buildUpstream : variant.buildUpstreamUrl,
|
|
92
|
+
cacheControl: (_config$cacheControl = config.cacheControl) !== null && _config$cacheControl !== void 0 ? _config$cacheControl : DEFAULT_CACHE_CONTROL,
|
|
93
|
+
encodePath: (_config$encodePath = config.encodePath) !== null && _config$encodePath !== void 0 ? _config$encodePath : variant.encodePath,
|
|
94
|
+
key,
|
|
95
|
+
loader: config.loader,
|
|
96
|
+
mediaHostEnvName
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
//#endregion
|
|
100
|
+
//#region \0@oxc-project+runtime@0.115.0/helpers/asyncToGenerator.js
|
|
101
|
+
function asyncGeneratorStep(n, t, e, r, o, a, c) {
|
|
102
|
+
try {
|
|
103
|
+
var i = n[a](c), u = i.value;
|
|
104
|
+
} catch (n) {
|
|
105
|
+
e(n);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
i.done ? t(u) : Promise.resolve(u).then(r, o);
|
|
109
|
+
}
|
|
110
|
+
function _asyncToGenerator(n) {
|
|
111
|
+
return function() {
|
|
112
|
+
var t = this, e = arguments;
|
|
113
|
+
return new Promise(function(r, o) {
|
|
114
|
+
var a = n.apply(t, e);
|
|
115
|
+
function _next(n) {
|
|
116
|
+
asyncGeneratorStep(a, r, o, _next, _throw, "next", n);
|
|
117
|
+
}
|
|
118
|
+
function _throw(n) {
|
|
119
|
+
asyncGeneratorStep(a, r, o, _next, _throw, "throw", n);
|
|
120
|
+
}
|
|
121
|
+
_next(void 0);
|
|
122
|
+
});
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
//#endregion
|
|
126
|
+
//#region src/image-optimizer/create-image-optimizer.ts
|
|
127
|
+
/**
|
|
128
|
+
* Creates a Next.js custom image `loader` and a proxy route-handler `GET`
|
|
129
|
+
* that together let an image CDN in front of the storefront (the Alokai
|
|
130
|
+
* Image Optimizer)
|
|
131
|
+
* optimize media-host images.
|
|
132
|
+
*
|
|
133
|
+
* The loader rewrites `src` values matching a configured media host to
|
|
134
|
+
* `/img-proxy/{key}/{path}?width=&quality=&format=auto`; the route handler
|
|
135
|
+
* proxies that path back to the media host and streams the body so the CDN
|
|
136
|
+
* can transform it on the way to the browser. Non-matching `src` values are
|
|
137
|
+
* returned unchanged.
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* ```ts
|
|
141
|
+
* // config/image-optimizer.ts - the single place the config exists; the
|
|
142
|
+
* // default export lets `images.loaderFile` point straight at this file
|
|
143
|
+
* import { createImageOptimizer } from "@vue-storefront/next";
|
|
144
|
+
*
|
|
145
|
+
* export const { GET, loader } = createImageOptimizer({ hosts: { ct: {} } });
|
|
146
|
+
*
|
|
147
|
+
* export default loader;
|
|
148
|
+
*
|
|
149
|
+
* // app/img-proxy/[host]/[...path]/route.ts
|
|
150
|
+
* export { GET } from "@/config/image-optimizer";
|
|
151
|
+
* ```
|
|
152
|
+
*/
|
|
153
|
+
function createImageOptimizer(config) {
|
|
154
|
+
if (Object.keys(config.hosts).length === 0) throw new Error([
|
|
155
|
+
"createImageOptimizer requires at least one host in \"hosts\".",
|
|
156
|
+
"Add the media hosts whose images the loader should optimize, e.g.:",
|
|
157
|
+
"",
|
|
158
|
+
" createImageOptimizer({",
|
|
159
|
+
" hosts: {",
|
|
160
|
+
" ct: {},",
|
|
161
|
+
" sapcc: { variant: \"sapcc\" },",
|
|
162
|
+
" },",
|
|
163
|
+
" });",
|
|
164
|
+
"",
|
|
165
|
+
"Each key becomes a URL segment and derives the env variable holding",
|
|
166
|
+
"the media host URL, e.g. \"ct\" reads NEXT_PUBLIC_CT_MEDIA_HOST."
|
|
167
|
+
].join("\n"));
|
|
168
|
+
const hosts = resolveHosts(config.hosts);
|
|
169
|
+
function loader({ quality, src, width }) {
|
|
170
|
+
for (const host of hosts) {
|
|
171
|
+
const mediaHost = env(host.mediaHostEnvName);
|
|
172
|
+
if (!mediaHost) {
|
|
173
|
+
getLogger().warning(`${host.mediaHostEnvName} is not defined, skipping image optimization for host "${host.key}".`);
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
const normalizedHost = mediaHost.replace(/\/$/, "");
|
|
177
|
+
if (!matchesMediaHost(src, normalizedHost)) continue;
|
|
178
|
+
if (host.loader) return host.loader({
|
|
179
|
+
quality,
|
|
180
|
+
src,
|
|
181
|
+
width
|
|
182
|
+
});
|
|
183
|
+
const localPath = src.slice(normalizedHost.length);
|
|
184
|
+
const queryIndex = localPath.indexOf("?");
|
|
185
|
+
const pathname = queryIndex === -1 ? localPath : localPath.slice(0, queryIndex);
|
|
186
|
+
const queryString = queryIndex === -1 ? "" : localPath.slice(queryIndex + 1);
|
|
187
|
+
const proxyPath = host.encodePath(pathname, new URLSearchParams(queryString));
|
|
188
|
+
if (proxyPath === void 0) return src;
|
|
189
|
+
return `${IMG_PROXY_PREFIX}/${host.key}${proxyPath}?width=${width}&quality=${quality || 85}&format=auto`;
|
|
190
|
+
}
|
|
191
|
+
return src;
|
|
192
|
+
}
|
|
193
|
+
function GET(_x, _x2) {
|
|
194
|
+
return _GET.apply(this, arguments);
|
|
195
|
+
}
|
|
196
|
+
function _GET() {
|
|
197
|
+
_GET = _asyncToGenerator(function* (_request, { params }) {
|
|
198
|
+
var _env, _upstream$headers$get;
|
|
199
|
+
const { host: hostKey, path } = yield params;
|
|
200
|
+
const host = hosts.find((entry) => entry.key === hostKey);
|
|
201
|
+
if (!host) return errorResponse(`Unknown image proxy host "${hostKey}"`, 404);
|
|
202
|
+
if (path.length === 0 || path.some(isUnsafeSegment)) return errorResponse("Invalid image path", 400);
|
|
203
|
+
const mediaHost = (_env = env(host.mediaHostEnvName)) === null || _env === void 0 ? void 0 : _env.replace(/\/$/, "");
|
|
204
|
+
if (!mediaHost) return errorResponse(`${host.mediaHostEnvName} is not defined`, 500);
|
|
205
|
+
let upstream;
|
|
206
|
+
try {
|
|
207
|
+
upstream = yield fetch(host.buildUpstreamUrl(mediaHost, path));
|
|
208
|
+
} catch (error) {
|
|
209
|
+
getLogger().error(`Failed to fetch upstream image for host "${hostKey}": ${String(error)}`);
|
|
210
|
+
return errorResponse("Failed to fetch upstream image", 502);
|
|
211
|
+
}
|
|
212
|
+
return new Response(upstream.body, {
|
|
213
|
+
headers: {
|
|
214
|
+
"cache-control": upstream.ok ? host.cacheControl : ERROR_CACHE_CONTROL,
|
|
215
|
+
"content-type": (_upstream$headers$get = upstream.headers.get("content-type")) !== null && _upstream$headers$get !== void 0 ? _upstream$headers$get : "application/octet-stream"
|
|
216
|
+
},
|
|
217
|
+
status: upstream.status
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
return _GET.apply(this, arguments);
|
|
221
|
+
}
|
|
222
|
+
return {
|
|
223
|
+
GET,
|
|
224
|
+
loader
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
let logger;
|
|
228
|
+
function getLogger() {
|
|
229
|
+
var _logger;
|
|
230
|
+
(_logger = logger) !== null && _logger !== void 0 || (logger = createLogger());
|
|
231
|
+
return logger;
|
|
232
|
+
}
|
|
233
|
+
function matchesMediaHost(src, normalizedHost) {
|
|
234
|
+
return src === normalizedHost || src.startsWith(`${normalizedHost}/`) || src.startsWith(`${normalizedHost}?`);
|
|
235
|
+
}
|
|
236
|
+
function isUnsafeSegment(segment) {
|
|
237
|
+
return segment === "" || segment === "." || segment === "..";
|
|
238
|
+
}
|
|
239
|
+
function errorResponse(message, status) {
|
|
240
|
+
return new Response(message, {
|
|
241
|
+
headers: { "cache-control": ERROR_CACHE_CONTROL },
|
|
242
|
+
status
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
//#endregion
|
|
21
246
|
//#region src/middleware.ts
|
|
22
247
|
/**
|
|
23
248
|
* Creates an Alokai middleware wrapper that adds pathname information to request headers.
|
|
@@ -62,32 +287,6 @@ const defaultMethodsRequestConfig = {
|
|
|
62
287
|
} }
|
|
63
288
|
};
|
|
64
289
|
//#endregion
|
|
65
|
-
//#region \0@oxc-project+runtime@0.115.0/helpers/asyncToGenerator.js
|
|
66
|
-
function asyncGeneratorStep(n, t, e, r, o, a, c) {
|
|
67
|
-
try {
|
|
68
|
-
var i = n[a](c), u = i.value;
|
|
69
|
-
} catch (n) {
|
|
70
|
-
e(n);
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
i.done ? t(u) : Promise.resolve(u).then(r, o);
|
|
74
|
-
}
|
|
75
|
-
function _asyncToGenerator(n) {
|
|
76
|
-
return function() {
|
|
77
|
-
var t = this, e = arguments;
|
|
78
|
-
return new Promise(function(r, o) {
|
|
79
|
-
var a = n.apply(t, e);
|
|
80
|
-
function _next(n) {
|
|
81
|
-
asyncGeneratorStep(a, r, o, _next, _throw, "next", n);
|
|
82
|
-
}
|
|
83
|
-
function _throw(n) {
|
|
84
|
-
asyncGeneratorStep(a, r, o, _next, _throw, "throw", n);
|
|
85
|
-
}
|
|
86
|
-
_next(void 0);
|
|
87
|
-
});
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
//#endregion
|
|
91
290
|
//#region src/sdk/helpers/resolveDynamicContext.ts
|
|
92
291
|
const BLACKLISTED_HEADERS = new Set(["host"]);
|
|
93
292
|
function isAppRouterHeaders(headers) {
|
|
@@ -234,4 +433,4 @@ function buildModules(modules) {
|
|
|
234
433
|
return (context) => Object.fromEntries(Object.entries(modules).map(([key, module]) => [key, module(context)]));
|
|
235
434
|
}
|
|
236
435
|
//#endregion
|
|
237
|
-
export { createAlokaiMiddleware, createLogger, createSdk, defineGetConfigSwitcherHeader, defineSdkConfig, defineSdkModule, env, resolveSdkOptions };
|
|
436
|
+
export { createAlokaiMiddleware, createImageOptimizer, createLogger, createSdk, defineGetConfigSwitcherHeader, defineSdkConfig, defineSdkModule, env, resolveSdkOptions };
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/vuestorefront/enterprise.git"
|
|
8
8
|
},
|
|
9
|
-
"version": "
|
|
9
|
+
"version": "8.0.0-next.3",
|
|
10
10
|
"exports": {
|
|
11
11
|
".": {
|
|
12
12
|
"types": "./dist/index.d.mts",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"zustand": "^4.5.4"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"@alokai/connect": "2.3.0-next.
|
|
38
|
+
"@alokai/connect": "2.3.0-next.7",
|
|
39
39
|
"@shared/typescript-config": "1.0.0",
|
|
40
40
|
"@types/react": "^19",
|
|
41
41
|
"@types/react-dom": "^19",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"vitest": "4.0.18"
|
|
51
51
|
},
|
|
52
52
|
"peerDependencies": {
|
|
53
|
-
"@alokai/connect": "2.3.0-next.
|
|
53
|
+
"@alokai/connect": "2.3.0-next.7",
|
|
54
54
|
"next": "^16.0.0",
|
|
55
55
|
"react": "^19.0.0"
|
|
56
56
|
},
|