@ecomiq/storefront 0.1.2 → 1.0.0
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/README.md +159 -53
- package/dist/index-C-xqLr1g.d.ts +6551 -0
- package/dist/nextjs/api.d.ts +10 -14
- package/dist/nextjs/api.js +74 -41
- package/dist/nextjs/api.js.map +1 -1
- package/dist/nextjs.d.ts +21 -3193
- package/dist/nextjs.js +1759 -588
- package/dist/nextjs.js.map +1 -1
- package/dist/store-proxy-handler-CNzKlTFA.d.ts +10 -0
- package/dist/tanstack/api.d.ts +41 -0
- package/dist/tanstack/api.js +196 -0
- package/dist/tanstack/api.js.map +1 -0
- package/dist/tanstack.d.ts +30 -0
- package/dist/{react.js → tanstack.js} +2466 -2172
- package/dist/tanstack.js.map +1 -0
- package/package.json +21 -13
- package/dist/collection-anMkdWa7.d.ts +0 -4333
- package/dist/core.d.ts +0 -3396
- package/dist/core.js +0 -1239
- package/dist/core.js.map +0 -1
- package/dist/find-storefront-cms-page-DNwLo1iV.d.ts +0 -178
- package/dist/react.d.ts +0 -964
- package/dist/react.js.map +0 -1
package/dist/nextjs/api.d.ts
CHANGED
|
@@ -1,24 +1,20 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { S as StoreProxyHandlerConfig } from '../store-proxy-handler-CNzKlTFA.js';
|
|
2
3
|
|
|
3
|
-
type StoreApiHandlerConfig =
|
|
4
|
-
/**
|
|
5
|
-
* Base URL del API de storefront. Default: "https://storefront.ecomiq.pe"
|
|
6
|
-
*/
|
|
7
|
-
apiBaseUrl?: string;
|
|
8
|
-
};
|
|
4
|
+
type StoreApiHandlerConfig = StoreProxyHandlerConfig;
|
|
9
5
|
/**
|
|
10
6
|
* Crea los handlers de ruta para montar la API de store como proxy en Next.js App Router.
|
|
11
|
-
* La base /v1/storefront se maneja internamente: el usuario llama /api/store/products
|
|
12
|
-
* y se hace proxy a https://storefront.ecomiq.pe/v1/storefront/products.
|
|
13
7
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* // app/api/store/[...path]/route.ts
|
|
10
|
+
* import { toNextJsStoreApiHandler } from "@ecomiq/storefront/nextjs/api";
|
|
16
11
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
12
|
+
* export const { GET, POST, PUT, PATCH, DELETE } = toNextJsStoreApiHandler({
|
|
13
|
+
* apiBaseUrl: process.env.STOREFRONT_API_URL,
|
|
14
|
+
* });
|
|
15
|
+
* ```
|
|
20
16
|
*/
|
|
21
|
-
declare function toNextJsStoreApiHandler(config
|
|
17
|
+
declare function toNextJsStoreApiHandler(config?: StoreApiHandlerConfig): {
|
|
22
18
|
GET: (request: NextRequest, context: {
|
|
23
19
|
params: Promise<{
|
|
24
20
|
path?: string[];
|
package/dist/nextjs/api.js
CHANGED
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server';
|
|
2
|
-
import { STOREFRONT_ECOMIQ_OAUTH_COOKIE_NAME, STOREFRONT_API_PATH_PREFIX } from '@ecomiq/storefront/core';
|
|
3
2
|
|
|
4
|
-
// src/react/nextjs/api/store-handler.ts
|
|
3
|
+
// src/react/frameworks/nextjs/api/store-handler.ts
|
|
4
|
+
|
|
5
|
+
// src/core/constants/api-paths.ts
|
|
6
|
+
var STOREFRONT_API_PATH_PREFIX = "v1";
|
|
7
|
+
|
|
8
|
+
// src/core/constants/storefront-ecomiq-oauth-cookie.ts
|
|
9
|
+
var STOREFRONT_ECOMIQ_OAUTH_COOKIE_NAME = "ecomiq_sf_oauth_at";
|
|
10
|
+
|
|
11
|
+
// src/core/server/store-proxy-handler.ts
|
|
5
12
|
var DEFAULT_API_BASE_URL = "https://storefront.ecomiq.pe";
|
|
13
|
+
function jsonError(status, message, error) {
|
|
14
|
+
return Response.json({ error, message, status }, { status });
|
|
15
|
+
}
|
|
6
16
|
async function ensureStoreContext(request) {
|
|
7
17
|
const xStoreId = request.headers.get("x-store-id")?.trim();
|
|
8
18
|
const xStoreDomain = request.headers.get("x-store-domain")?.trim();
|
|
@@ -16,7 +26,11 @@ async function ensureStoreContext(request) {
|
|
|
16
26
|
if (!xStoreId) {
|
|
17
27
|
return {
|
|
18
28
|
ok: false,
|
|
19
|
-
response:
|
|
29
|
+
response: jsonError(
|
|
30
|
+
400,
|
|
31
|
+
"X-Store-Id is required",
|
|
32
|
+
"Store context required"
|
|
33
|
+
)
|
|
20
34
|
};
|
|
21
35
|
}
|
|
22
36
|
return {
|
|
@@ -32,17 +46,10 @@ var SKIP_FORWARD_HEADERS = /* @__PURE__ */ new Set([
|
|
|
32
46
|
"transfer-encoding",
|
|
33
47
|
"x-store-id",
|
|
34
48
|
"x-store-domain",
|
|
35
|
-
/** Se reconstruye abajo desde el header entrante al proxy. */
|
|
36
49
|
"authorization",
|
|
37
|
-
/** Nunca reenviar sesión/cookies del storefront al API Ecomiq (solo headers operativos). */
|
|
38
50
|
"cookie",
|
|
39
51
|
"cookie2",
|
|
40
52
|
"set-cookie",
|
|
41
|
-
/**
|
|
42
|
-
* Headers de proxy/origen de Cloudflare: se reconstruyen abajo (X-Forwarded-*)
|
|
43
|
-
* para no contaminar la cadena con valores que el upstream no debe leer directamente
|
|
44
|
-
* y para garantizar que la IP del cliente final llegue como X-Forwarded-For.
|
|
45
|
-
*/
|
|
46
53
|
"x-forwarded-for",
|
|
47
54
|
"x-forwarded-proto",
|
|
48
55
|
"x-forwarded-host",
|
|
@@ -72,11 +79,23 @@ function getClientIp(request) {
|
|
|
72
79
|
}
|
|
73
80
|
return null;
|
|
74
81
|
}
|
|
82
|
+
function getCookieValue(request, name) {
|
|
83
|
+
const cookieHeader = request.headers.get("cookie");
|
|
84
|
+
if (!cookieHeader) return void 0;
|
|
85
|
+
for (const part of cookieHeader.split(";")) {
|
|
86
|
+
const [rawKey, ...rest] = part.trim().split("=");
|
|
87
|
+
if (rawKey?.trim() === name) {
|
|
88
|
+
return decodeURIComponent(rest.join("="));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return void 0;
|
|
92
|
+
}
|
|
75
93
|
function buildUpstreamRequest(request, pathSegments, config, storeId, storeDomain) {
|
|
94
|
+
const requestUrl = new URL(request.url);
|
|
76
95
|
const base = (config.apiBaseUrl ?? DEFAULT_API_BASE_URL).replace(/\/$/, "");
|
|
77
96
|
const userPath = pathSegments.length ? pathSegments.join("/") : "";
|
|
78
97
|
const path = userPath ? `${STOREFRONT_API_PATH_PREFIX}/${userPath}` : STOREFRONT_API_PATH_PREFIX;
|
|
79
|
-
const search =
|
|
98
|
+
const search = requestUrl.searchParams.toString();
|
|
80
99
|
const url = search ? `${base}/${path}?${search}` : `${base}/${path}`;
|
|
81
100
|
const headers = new Headers();
|
|
82
101
|
request.headers.forEach((value, key) => {
|
|
@@ -93,14 +112,14 @@ function buildUpstreamRequest(request, pathSegments, config, storeId, storeDomai
|
|
|
93
112
|
if (clientIp) {
|
|
94
113
|
headers.set("X-Forwarded-For", clientIp);
|
|
95
114
|
}
|
|
96
|
-
const forwardedProto = request.headers.get("cf-visitor")?.match(/"scheme":"(https?)"/)?.[1] ??
|
|
115
|
+
const forwardedProto = request.headers.get("cf-visitor")?.match(/"scheme":"(https?)"/)?.[1] ?? requestUrl.protocol.replace(":", "") ?? "https";
|
|
97
116
|
headers.set("X-Forwarded-Proto", forwardedProto);
|
|
98
|
-
const forwardedHost = request.headers.get("host") ??
|
|
117
|
+
const forwardedHost = request.headers.get("host") ?? requestUrl.host;
|
|
99
118
|
if (forwardedHost) {
|
|
100
119
|
headers.set("X-Forwarded-Host", forwardedHost);
|
|
101
120
|
}
|
|
102
121
|
const incomingAuth = request.headers.get("authorization")?.trim();
|
|
103
|
-
const storefrontOAuth = request
|
|
122
|
+
const storefrontOAuth = getCookieValue(request, STOREFRONT_ECOMIQ_OAUTH_COOKIE_NAME)?.trim();
|
|
104
123
|
if (incomingAuth && /^Bearer\s+\S+/i.test(incomingAuth)) {
|
|
105
124
|
headers.set("Authorization", incomingAuth);
|
|
106
125
|
} else if (storefrontOAuth) {
|
|
@@ -121,39 +140,53 @@ function buildUpstreamRequest(request, pathSegments, config, storeId, storeDomai
|
|
|
121
140
|
function isResponseBodyForbidden(status) {
|
|
122
141
|
return status === 204 || status === 205 || status === 304;
|
|
123
142
|
}
|
|
124
|
-
function
|
|
143
|
+
async function handleStoreProxyRequest(request, pathSegments, config = {}) {
|
|
144
|
+
const storeContext = await ensureStoreContext(request);
|
|
145
|
+
if (!storeContext.ok) {
|
|
146
|
+
return storeContext.response;
|
|
147
|
+
}
|
|
148
|
+
const { storeId, storeDomain } = storeContext;
|
|
149
|
+
const { url, init } = buildUpstreamRequest(
|
|
150
|
+
request,
|
|
151
|
+
pathSegments,
|
|
152
|
+
config,
|
|
153
|
+
storeId,
|
|
154
|
+
storeDomain
|
|
155
|
+
);
|
|
156
|
+
const upstream = await fetch(url, init);
|
|
157
|
+
const responseHeaders = new Headers(upstream.headers);
|
|
158
|
+
const status = upstream.status;
|
|
159
|
+
if (isResponseBodyForbidden(status)) {
|
|
160
|
+
responseHeaders.delete("content-length");
|
|
161
|
+
responseHeaders.delete("content-type");
|
|
162
|
+
return new Response(null, {
|
|
163
|
+
status,
|
|
164
|
+
statusText: upstream.statusText,
|
|
165
|
+
headers: responseHeaders
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
const body = await upstream.text();
|
|
169
|
+
return new Response(body, {
|
|
170
|
+
status,
|
|
171
|
+
statusText: upstream.statusText,
|
|
172
|
+
headers: responseHeaders
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// src/react/frameworks/nextjs/api/store-handler.ts
|
|
177
|
+
function toNextJsStoreApiHandler(config = {}) {
|
|
125
178
|
const handler = async (request, context) => {
|
|
126
179
|
const params = await context.params;
|
|
127
180
|
const pathSegments = params.path ?? [];
|
|
128
|
-
const
|
|
129
|
-
if (!storeContext.ok) {
|
|
130
|
-
return storeContext.response;
|
|
131
|
-
}
|
|
132
|
-
const { storeId, storeDomain } = storeContext;
|
|
133
|
-
const { url, init } = buildUpstreamRequest(
|
|
181
|
+
const response = await handleStoreProxyRequest(
|
|
134
182
|
request,
|
|
135
183
|
pathSegments,
|
|
136
|
-
config
|
|
137
|
-
storeId,
|
|
138
|
-
storeDomain
|
|
184
|
+
config
|
|
139
185
|
);
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
responseHeaders.delete("content-length");
|
|
145
|
-
responseHeaders.delete("content-type");
|
|
146
|
-
return new NextResponse(null, {
|
|
147
|
-
status,
|
|
148
|
-
statusText: upstream.statusText,
|
|
149
|
-
headers: responseHeaders
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
const body = await upstream.text();
|
|
153
|
-
return new NextResponse(body, {
|
|
154
|
-
status,
|
|
155
|
-
statusText: upstream.statusText,
|
|
156
|
-
headers: responseHeaders
|
|
186
|
+
return new NextResponse(response.body, {
|
|
187
|
+
status: response.status,
|
|
188
|
+
statusText: response.statusText,
|
|
189
|
+
headers: response.headers
|
|
157
190
|
});
|
|
158
191
|
};
|
|
159
192
|
return {
|
package/dist/nextjs/api.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/react/nextjs/api/store-handler.ts"],"names":[],"mappings":"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","file":"api.js","sourcesContent":["import type { NextRequest } from \"next/server\";\nimport { NextResponse } from \"next/server\";\nimport {\n STOREFRONT_API_PATH_PREFIX,\n STOREFRONT_ECOMIQ_OAUTH_COOKIE_NAME,\n} from \"@ecomiq/storefront/core\";\n\nconst DEFAULT_API_BASE_URL = \"https://storefront.ecomiq.pe\";\n\nexport type StoreApiHandlerConfig = {\n /**\n * Base URL del API de storefront. Default: \"https://storefront.ecomiq.pe\"\n */\n apiBaseUrl?: string;\n};\n\ntype StoreContextResult =\n | {\n ok: true;\n storeId: string;\n storeDomain: string | null;\n }\n | { ok: false; response: NextResponse };\n\nasync function ensureStoreContext(request: NextRequest): Promise<StoreContextResult> {\n const xStoreId = request.headers.get(\"x-store-id\")?.trim();\n const xStoreDomain = request.headers.get(\"x-store-domain\")?.trim();\n if (xStoreDomain) {\n return {\n ok: true,\n storeId: xStoreId || \"\",\n storeDomain: xStoreDomain || null,\n };\n }\n if (!xStoreId) {\n return {\n ok: false,\n response: NextResponse.json({ error: \"Store context required\", message: \"X-Store-Id is required\", status: 400 }),\n };\n }\n\n return {\n ok: true,\n storeId: xStoreId || \"\",\n storeDomain: xStoreDomain || null,\n };\n}\n\n/**\n * Headers que no se reenvían al upstream Ecomiq.\n * La API remota no usa cookies del navegador para el contexto de tienda: toda petición al proxy debe traer\n * `X-Store-Id`. `X-Store-Domain` es opcional para reenvío al upstream. El token sale de `Authorization` /\n * cookie OAuth leída en el proxy.\n */\nconst SKIP_FORWARD_HEADERS = new Set([\n \"host\",\n \"connection\",\n \"content-length\",\n \"transfer-encoding\",\n \"x-store-id\",\n \"x-store-domain\",\n /** Se reconstruye abajo desde el header entrante al proxy. */\n \"authorization\",\n /** Nunca reenviar sesión/cookies del storefront al API Ecomiq (solo headers operativos). */\n \"cookie\",\n \"cookie2\",\n \"set-cookie\",\n /**\n * Headers de proxy/origen de Cloudflare: se reconstruyen abajo (X-Forwarded-*)\n * para no contaminar la cadena con valores que el upstream no debe leer directamente\n * y para garantizar que la IP del cliente final llegue como X-Forwarded-For.\n */\n \"x-forwarded-for\",\n \"x-forwarded-proto\",\n \"x-forwarded-host\",\n \"x-real-ip\",\n \"forwarded\",\n \"cf-connecting-ip\",\n \"cf-connecting-ipv6\",\n \"true-client-ip\",\n \"cf-ipcountry\",\n \"cf-ray\",\n \"cf-visitor\",\n]);\n\n/**\n * Obtiene la IP real del cliente (no la IP del worker/edge de Cloudflare).\n * En Cloudflare Workers, `cf-connecting-ip` siempre es la IP del cliente final.\n */\nfunction getClientIp(request: NextRequest): string | null {\n const candidates = [\n request.headers.get(\"cf-connecting-ip\"),\n request.headers.get(\"true-client-ip\"),\n request.headers.get(\"x-real-ip\"),\n ];\n for (const value of candidates) {\n const trimmed = value?.trim();\n if (trimmed) return trimmed;\n }\n const xff = request.headers.get(\"x-forwarded-for\");\n if (xff?.trim()) {\n const first = xff.split(\",\")[0]?.trim();\n if (first) return first;\n }\n return null;\n}\n\nfunction buildUpstreamRequest(\n request: NextRequest,\n pathSegments: string[],\n config: StoreApiHandlerConfig,\n storeId: string,\n storeDomain: string | null\n): { url: string; init: RequestInit } {\n const base = (config.apiBaseUrl ?? DEFAULT_API_BASE_URL).replace(/\\/$/, \"\");\n const userPath = pathSegments.length ? pathSegments.join(\"/\") : \"\";\n const path = userPath\n ? `${STOREFRONT_API_PATH_PREFIX}/${userPath}`\n : STOREFRONT_API_PATH_PREFIX;\n const search = request.nextUrl.searchParams.toString();\n const url = search ? `${base}/${path}?${search}` : `${base}/${path}`;\n\n const headers = new Headers();\n request.headers.forEach((value, key) => {\n const lower = key.toLowerCase();\n if (!SKIP_FORWARD_HEADERS.has(lower)) {\n headers.set(key, value);\n }\n });\n headers.set(\"X-Store-Id\", storeId);\n if (storeDomain) {\n headers.set(\"X-Store-domain\", storeDomain);\n }\n\n // Reenvío de IP/origen del cliente final hacia apiecomiq.\n // Sin esto, todas las requests llegan con la IP del worker de Cloudflare\n // y caen en el rate limit del backend (.NET ForwardedHeaders middleware\n // lee X-Forwarded-For / -Proto / -Host).\n const clientIp = getClientIp(request);\n if (clientIp) {\n headers.set(\"X-Forwarded-For\", clientIp);\n }\n const forwardedProto =\n request.headers.get(\"cf-visitor\")?.match(/\"scheme\":\"(https?)\"/)?.[1] ??\n request.nextUrl.protocol.replace(\":\", \"\") ??\n \"https\";\n headers.set(\"X-Forwarded-Proto\", forwardedProto);\n const forwardedHost =\n request.headers.get(\"host\") ?? request.nextUrl.host;\n if (forwardedHost) {\n headers.set(\"X-Forwarded-Host\", forwardedHost);\n }\n\n const incomingAuth = request.headers.get(\"authorization\")?.trim();\n const storefrontOAuth =\n request.cookies.get(STOREFRONT_ECOMIQ_OAUTH_COOKIE_NAME)?.value?.trim();\n\n if (incomingAuth && /^Bearer\\s+\\S+/i.test(incomingAuth)) {\n headers.set(\"Authorization\", incomingAuth);\n } else if (storefrontOAuth) {\n headers.set(\"Authorization\", `Bearer ${storefrontOAuth}`);\n }\n headers.set(\"Accept-Encoding\", \"identity\");\n\n const init: RequestInit & { duplex?: \"half\" } = {\n method: request.method,\n headers,\n };\n\n const bodyMethods = [\"POST\", \"PUT\", \"PATCH\"];\n if (bodyMethods.includes(request.method) && request.body) {\n init.body = request.body;\n init.duplex = \"half\";\n }\n\n return { url, init };\n}\n\n/** Códigos HTTP que no pueden llevar cuerpo (Fetch / NextResponse). */\nfunction isResponseBodyForbidden(status: number): boolean {\n return status === 204 || status === 205 || status === 304;\n}\n\n/**\n * Crea los handlers de ruta para montar la API de store como proxy en Next.js App Router.\n * La base /v1/storefront se maneja internamente: el usuario llama /api/store/products\n * y se hace proxy a https://storefront.ecomiq.pe/v1/storefront/products.\n *\n * // app/api/store/[...path]/route.ts\n * import { toNextJsStoreApiHandler } from \"@ecomiq/storefront/nextjs/api\";\n *\n * export const { GET, POST, PUT, PATCH, DELETE } = toNextJsStoreApiHandler({\n * apiBaseUrl: \"https://storefront.ecomiq.pe\", // opcional, es el default\n * });\n */\nexport function toNextJsStoreApiHandler(config: StoreApiHandlerConfig) {\n const handler = async (\n request: NextRequest,\n context: { params: Promise<{ path?: string[] }> }\n ): Promise<NextResponse> => {\n const params = await context.params;\n const pathSegments = params.path ?? [];\n\n const storeContext = await ensureStoreContext(request);\n if (!storeContext.ok) {\n return storeContext.response;\n }\n\n const { storeId, storeDomain } = storeContext;\n const { url, init } = buildUpstreamRequest(\n request,\n pathSegments,\n config,\n storeId,\n storeDomain\n );\n\n const upstream = await fetch(url, init);\n const responseHeaders = new Headers(upstream.headers);\n const status = upstream.status;\n\n if (isResponseBodyForbidden(status)) {\n responseHeaders.delete(\"content-length\");\n responseHeaders.delete(\"content-type\");\n return new NextResponse(null, {\n status,\n statusText: upstream.statusText,\n headers: responseHeaders,\n });\n }\n\n const body = await upstream.text();\n return new NextResponse(body, {\n status,\n statusText: upstream.statusText,\n headers: responseHeaders,\n });\n };\n\n return {\n GET: handler,\n POST: handler,\n PUT: handler,\n PATCH: handler,\n DELETE: handler,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/core/constants/api-paths.ts","../../src/core/constants/storefront-ecomiq-oauth-cookie.ts","../../src/core/server/store-proxy-handler.ts","../../src/react/frameworks/nextjs/api/store-handler.ts"],"names":[],"mappings":"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","file":"api.js","sourcesContent":["/** Prefijo base de la API storefront (ej. v1/storefront). */\nexport const STOREFRONT_API_PATH_PREFIX = \"v1\";\n","/** Cookie HttpOnly con el bearer OAuth storefront; solo path `/api/store` (no navega a todo el sitio). */\nexport const STOREFRONT_ECOMIQ_OAUTH_COOKIE_NAME = \"ecomiq_sf_oauth_at\";\n\n/** Debe coincidir con el prefijo de la ruta proxy en Next (`app/api/store/...`). */\nexport const STOREFRONT_ECOMIQ_OAUTH_COOKIE_PATH = \"/api/store\";\n","import { STOREFRONT_API_PATH_PREFIX } from \"../constants/api-paths\";\nimport { STOREFRONT_ECOMIQ_OAUTH_COOKIE_NAME } from \"../constants/storefront-ecomiq-oauth-cookie\";\n\nconst DEFAULT_API_BASE_URL = \"https://storefront.ecomiq.pe\";\n\nexport type StoreProxyHandlerConfig = {\n /** Base URL del API de storefront. Default: `https://storefront.ecomiq.pe` */\n apiBaseUrl?: string;\n};\n\ntype StoreContextResult =\n | {\n ok: true;\n storeId: string;\n storeDomain: string | null;\n }\n | { ok: false; response: Response };\n\nfunction jsonError(status: number, message: string, error: string): Response {\n return Response.json({ error, message, status }, { status });\n}\n\nasync function ensureStoreContext(request: Request): Promise<StoreContextResult> {\n const xStoreId = request.headers.get(\"x-store-id\")?.trim();\n const xStoreDomain = request.headers.get(\"x-store-domain\")?.trim();\n if (xStoreDomain) {\n return {\n ok: true,\n storeId: xStoreId || \"\",\n storeDomain: xStoreDomain || null,\n };\n }\n if (!xStoreId) {\n return {\n ok: false,\n response: jsonError(\n 400,\n \"X-Store-Id is required\",\n \"Store context required\",\n ),\n };\n }\n\n return {\n ok: true,\n storeId: xStoreId || \"\",\n storeDomain: xStoreDomain || null,\n };\n}\n\nconst SKIP_FORWARD_HEADERS = new Set([\n \"host\",\n \"connection\",\n \"content-length\",\n \"transfer-encoding\",\n \"x-store-id\",\n \"x-store-domain\",\n \"authorization\",\n \"cookie\",\n \"cookie2\",\n \"set-cookie\",\n \"x-forwarded-for\",\n \"x-forwarded-proto\",\n \"x-forwarded-host\",\n \"x-real-ip\",\n \"forwarded\",\n \"cf-connecting-ip\",\n \"cf-connecting-ipv6\",\n \"true-client-ip\",\n \"cf-ipcountry\",\n \"cf-ray\",\n \"cf-visitor\",\n]);\n\nfunction getClientIp(request: Request): string | null {\n const candidates = [\n request.headers.get(\"cf-connecting-ip\"),\n request.headers.get(\"true-client-ip\"),\n request.headers.get(\"x-real-ip\"),\n ];\n for (const value of candidates) {\n const trimmed = value?.trim();\n if (trimmed) return trimmed;\n }\n const xff = request.headers.get(\"x-forwarded-for\");\n if (xff?.trim()) {\n const first = xff.split(\",\")[0]?.trim();\n if (first) return first;\n }\n return null;\n}\n\nfunction getCookieValue(request: Request, name: string): string | undefined {\n const cookieHeader = request.headers.get(\"cookie\");\n if (!cookieHeader) return undefined;\n for (const part of cookieHeader.split(\";\")) {\n const [rawKey, ...rest] = part.trim().split(\"=\");\n if (rawKey?.trim() === name) {\n return decodeURIComponent(rest.join(\"=\"));\n }\n }\n return undefined;\n}\n\nfunction buildUpstreamRequest(\n request: Request,\n pathSegments: string[],\n config: StoreProxyHandlerConfig,\n storeId: string,\n storeDomain: string | null,\n): { url: string; init: RequestInit & { duplex?: \"half\" } } {\n const requestUrl = new URL(request.url);\n const base = (config.apiBaseUrl ?? DEFAULT_API_BASE_URL).replace(/\\/$/, \"\");\n const userPath = pathSegments.length ? pathSegments.join(\"/\") : \"\";\n const path = userPath\n ? `${STOREFRONT_API_PATH_PREFIX}/${userPath}`\n : STOREFRONT_API_PATH_PREFIX;\n const search = requestUrl.searchParams.toString();\n const url = search ? `${base}/${path}?${search}` : `${base}/${path}`;\n\n const headers = new Headers();\n request.headers.forEach((value, key) => {\n const lower = key.toLowerCase();\n if (!SKIP_FORWARD_HEADERS.has(lower)) {\n headers.set(key, value);\n }\n });\n headers.set(\"X-Store-Id\", storeId);\n if (storeDomain) {\n headers.set(\"X-Store-domain\", storeDomain);\n }\n\n const clientIp = getClientIp(request);\n if (clientIp) {\n headers.set(\"X-Forwarded-For\", clientIp);\n }\n const forwardedProto =\n request.headers.get(\"cf-visitor\")?.match(/\"scheme\":\"(https?)\"/)?.[1] ??\n requestUrl.protocol.replace(\":\", \"\") ??\n \"https\";\n headers.set(\"X-Forwarded-Proto\", forwardedProto);\n const forwardedHost = request.headers.get(\"host\") ?? requestUrl.host;\n if (forwardedHost) {\n headers.set(\"X-Forwarded-Host\", forwardedHost);\n }\n\n const incomingAuth = request.headers.get(\"authorization\")?.trim();\n const storefrontOAuth = getCookieValue(request, STOREFRONT_ECOMIQ_OAUTH_COOKIE_NAME)?.trim();\n\n if (incomingAuth && /^Bearer\\s+\\S+/i.test(incomingAuth)) {\n headers.set(\"Authorization\", incomingAuth);\n } else if (storefrontOAuth) {\n headers.set(\"Authorization\", `Bearer ${storefrontOAuth}`);\n }\n headers.set(\"Accept-Encoding\", \"identity\");\n\n const init: RequestInit & { duplex?: \"half\" } = {\n method: request.method,\n headers,\n };\n\n const bodyMethods = [\"POST\", \"PUT\", \"PATCH\"];\n if (bodyMethods.includes(request.method) && request.body) {\n init.body = request.body;\n init.duplex = \"half\";\n }\n\n return { url, init };\n}\n\nfunction isResponseBodyForbidden(status: number): boolean {\n return status === 204 || status === 205 || status === 304;\n}\n\n/**\n * Proxy BFF `/api/store/*` → upstream Ecomiq. Usa Web Fetch API (`Request` / `Response`).\n */\nexport async function handleStoreProxyRequest(\n request: Request,\n pathSegments: string[],\n config: StoreProxyHandlerConfig = {},\n): Promise<Response> {\n const storeContext = await ensureStoreContext(request);\n if (!storeContext.ok) {\n return storeContext.response;\n }\n\n const { storeId, storeDomain } = storeContext;\n const { url, init } = buildUpstreamRequest(\n request,\n pathSegments,\n config,\n storeId,\n storeDomain,\n );\n\n const upstream = await fetch(url, init);\n const responseHeaders = new Headers(upstream.headers);\n const status = upstream.status;\n\n if (isResponseBodyForbidden(status)) {\n responseHeaders.delete(\"content-length\");\n responseHeaders.delete(\"content-type\");\n return new Response(null, {\n status,\n statusText: upstream.statusText,\n headers: responseHeaders,\n });\n }\n\n const body = await upstream.text();\n return new Response(body, {\n status,\n statusText: upstream.statusText,\n headers: responseHeaders,\n });\n}\n","import type { NextRequest } from \"next/server\";\nimport { NextResponse } from \"next/server\";\nimport {\n handleStoreProxyRequest,\n type StoreProxyHandlerConfig,\n} from \"../../../../core/server/store-proxy-handler\";\n\nexport type StoreApiHandlerConfig = StoreProxyHandlerConfig;\n\n/**\n * Crea los handlers de ruta para montar la API de store como proxy en Next.js App Router.\n *\n * ```ts\n * // app/api/store/[...path]/route.ts\n * import { toNextJsStoreApiHandler } from \"@ecomiq/storefront/nextjs/api\";\n *\n * export const { GET, POST, PUT, PATCH, DELETE } = toNextJsStoreApiHandler({\n * apiBaseUrl: process.env.STOREFRONT_API_URL,\n * });\n * ```\n */\nexport function toNextJsStoreApiHandler(config: StoreApiHandlerConfig = {}) {\n const handler = async (\n request: NextRequest,\n context: { params: Promise<{ path?: string[] }> },\n ): Promise<NextResponse> => {\n const params = await context.params;\n const pathSegments = params.path ?? [];\n const response = await handleStoreProxyRequest(\n request,\n pathSegments,\n config,\n );\n return new NextResponse(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers: response.headers,\n });\n };\n\n return {\n GET: handler,\n POST: handler,\n PUT: handler,\n PATCH: handler,\n DELETE: handler,\n };\n}\n\nexport { handleStoreProxyRequest } from \"../../../../core/server/store-proxy-handler\";\n"]}
|