@ecomiq/storefront 1.1.5 → 2.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 +26 -417
- package/bin/ecomiq.mjs +154 -0
- package/package.json +72 -84
- package/src/api-proxy.ts +194 -0
- package/src/auth/create-storefront-auth.ts +35 -0
- package/src/auth/ecomiq-oauth-password-client.ts +26 -0
- package/src/auth/ecomiq-oauth-password-constants.ts +26 -0
- package/src/auth/ecomiq-oauth-password-plugin.ts +299 -0
- package/src/auth/ecomiq-oauth-password-types.ts +25 -0
- package/src/auth/ecomiq-storefront-sign-in.ts +47 -0
- package/src/auth/env.ts +7 -0
- package/src/cart-session.ts +241 -0
- package/src/client.ts +48 -0
- package/src/codegen.mjs +603 -0
- package/src/components/checkout/cart-sync.ts +143 -0
- package/src/components/checkout/checkout-external-update-banner.tsx +15 -0
- package/src/components/checkout/checkout-form-controls.tsx +139 -0
- package/src/components/checkout/checkout-line-items.tsx +43 -0
- package/src/components/checkout/checkout-order-summary.tsx +156 -0
- package/src/components/checkout/checkout-payment-section.tsx +215 -0
- package/src/components/checkout/checkout-session-view.tsx +240 -0
- package/src/components/checkout/checkout-shipping-address-section.tsx +266 -0
- package/src/components/checkout/checkout-shipping-section.tsx +90 -0
- package/src/components/checkout/checkout-states.tsx +73 -0
- package/src/components/checkout/form-schema.ts +229 -0
- package/src/components/checkout/format.ts +27 -0
- package/src/components/checkout/index.ts +51 -0
- package/src/components/checkout/types.ts +43 -0
- package/src/components/checkout/use-checkout-geo.ts +50 -0
- package/src/components/checkout/use-checkout-session.ts +583 -0
- package/src/components/hero.tsx +291 -0
- package/src/config.d.ts +69 -0
- package/src/config.mjs +165 -0
- package/src/index.ts +274 -0
- package/src/link.tsx +2 -0
- package/src/pages/checkout.$checkoutId.tsx +60 -0
- package/src/pages/checkout.tsx +31 -0
- package/src/pages/thank-you.tsx +53 -0
- package/src/providers/auth.tsx +69 -0
- package/src/providers/cart.tsx +275 -0
- package/src/providers/ecomiq.ts +137 -0
- package/src/providers/index.ts +26 -0
- package/src/public-config.ts +44 -0
- package/src/review-submission.ts +89 -0
- package/src/runtime/context.ts +1 -0
- package/src/runtime/default-layout.tsx +80 -0
- package/src/runtime/head.ts +126 -0
- package/src/runtime/page.tsx +82 -0
- package/src/runtime/postcss-browser-stub.mjs +5 -0
- package/src/server-config-store.ts +26 -0
- package/src/server.ts +279 -0
- package/src/styles.css +76 -0
- package/src/ui/badge.tsx +32 -0
- package/src/ui/button.tsx +31 -0
- package/src/ui/card.tsx +31 -0
- package/src/ui/carousel.tsx +218 -0
- package/src/ui/cn.ts +6 -0
- package/src/ui/index.ts +8 -0
- package/src/ui/separator.tsx +6 -0
- package/src/ui/skeleton.tsx +6 -0
- package/src/ui/storefront-error.tsx +49 -0
- package/src/utils.ts +36 -0
- package/src/vite-inline.mjs +95 -0
- package/templates/__root.tsx +77 -0
- package/templates/router.tsx +19 -0
- package/dist/chunk-3QGB55R6.js +0 -157
- package/dist/chunk-3QGB55R6.js.map +0 -1
- package/dist/chunk-64GLOWMJ.js +0 -216
- package/dist/chunk-64GLOWMJ.js.map +0 -1
- package/dist/chunk-ASFU32ZM.js +0 -236
- package/dist/chunk-ASFU32ZM.js.map +0 -1
- package/dist/chunk-Q4U5OEUT.js +0 -21
- package/dist/chunk-Q4U5OEUT.js.map +0 -1
- package/dist/chunk-QRR7EPCH.js +0 -176
- package/dist/chunk-QRR7EPCH.js.map +0 -1
- package/dist/chunk-S7CPZ4UE.js +0 -6
- package/dist/chunk-S7CPZ4UE.js.map +0 -1
- package/dist/chunk-ZG2XUYGZ.js +0 -1281
- package/dist/chunk-ZG2XUYGZ.js.map +0 -1
- package/dist/chunks/client.css +0 -984
- package/dist/chunks/client.css.map +0 -1
- package/dist/chunks/client.d.ts +0 -10041
- package/dist/chunks/client.js +0 -5363
- package/dist/chunks/client.js.map +0 -1
- package/dist/chunks/config.d.ts +0 -31
- package/dist/chunks/config.js +0 -5
- package/dist/chunks/config.js.map +0 -1
- package/dist/chunks/core-public.d.ts +0 -130
- package/dist/chunks/core-public.js +0 -92
- package/dist/chunks/core-public.js.map +0 -1
- package/dist/chunks/server-nextjs.d.ts +0 -33
- package/dist/chunks/server-nextjs.js +0 -47
- package/dist/chunks/server-nextjs.js.map +0 -1
- package/dist/chunks/server-tanstack.d.ts +0 -22
- package/dist/chunks/server-tanstack.js +0 -34
- package/dist/chunks/server-tanstack.js.map +0 -1
- package/dist/collection-C6bWDLiw.d.ts +0 -3926
- package/dist/ecomiq-oauth-password-plugin-J5wDFqzL.d.ts +0 -3629
- package/dist/ecomiq-shell-ui-SWIFLQVM.js +0 -8
- package/dist/ecomiq-shell-ui-SWIFLQVM.js.map +0 -1
- package/dist/index.d.ts +0 -9
- package/dist/index.js +0 -1
- package/dist/index.js.map +0 -1
- package/dist/index.server.d.ts +0 -9
- package/dist/index.server.js +0 -8
- package/dist/index.server.js.map +0 -1
- package/dist/nextjs/api.d.ts +0 -45
- package/dist/nextjs/api.js +0 -32
- package/dist/nextjs/api.js.map +0 -1
- package/dist/nextjs/globals.css +0 -13
- package/dist/nextjs.d.ts +0 -2
- package/dist/nextjs.js +0 -2
- package/dist/nextjs.js.map +0 -1
- package/dist/nextjs.server.d.ts +0 -1
- package/dist/nextjs.server.js +0 -1
- package/dist/nextjs.server.js.map +0 -1
- package/dist/postcss.config.mjs +0 -6
- package/dist/react/checkout-page.css +0 -855
- package/dist/react/client.css +0 -984
- package/dist/react/payment-provider.css +0 -91
- package/dist/react/thank-you-page.css +0 -105
- package/dist/react.d.ts +0 -1
- package/dist/react.js +0 -2
- package/dist/react.js.map +0 -1
- package/dist/store-proxy-handler-CNzKlTFA.d.ts +0 -10
- package/dist/tanstack/api.d.ts +0 -41
- package/dist/tanstack/api.js +0 -30
- package/dist/tanstack/api.js.map +0 -1
- package/dist/tanstack.d.ts +0 -2
- package/dist/tanstack.js +0 -2
- package/dist/tanstack.js.map +0 -1
- package/dist/tanstack.server.d.ts +0 -1
- package/dist/tanstack.server.js +0 -1
- package/dist/tanstack.server.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,101 +1,89 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ecomiq/storefront",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Micro framework SSR de Ecomiq para storefronts. Páginas, datos y deploy sin configurar nada.",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"
|
|
7
|
-
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Ecomiq",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/ecomiq/ecomiq-storefront.git",
|
|
11
|
+
"directory": "packages/ecomiq-storefront"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/ecomiq/ecomiq-storefront/tree/main/packages/ecomiq-storefront",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/ecomiq/ecomiq-storefront/issues"
|
|
8
16
|
},
|
|
9
17
|
"keywords": [
|
|
10
18
|
"ecomiq",
|
|
11
19
|
"storefront",
|
|
12
20
|
"ecommerce",
|
|
13
|
-
"
|
|
14
|
-
"tanstack
|
|
15
|
-
"
|
|
16
|
-
],
|
|
17
|
-
"sideEffects": [
|
|
18
|
-
"**/*.css"
|
|
21
|
+
"ssr",
|
|
22
|
+
"tanstack",
|
|
23
|
+
"cloudflare"
|
|
19
24
|
],
|
|
25
|
+
"bin": {
|
|
26
|
+
"ecomiq": "./bin/ecomiq.mjs"
|
|
27
|
+
},
|
|
28
|
+
"exports": {
|
|
29
|
+
".": "./src/index.ts",
|
|
30
|
+
"./config": {
|
|
31
|
+
"types": "./src/config.d.ts",
|
|
32
|
+
"default": "./src/config.mjs"
|
|
33
|
+
},
|
|
34
|
+
"./ui": "./src/ui/index.ts",
|
|
35
|
+
"./styles.css": "./src/styles.css",
|
|
36
|
+
"./internal/page": "./src/runtime/page.tsx",
|
|
37
|
+
"./internal/context": "./src/runtime/context.ts",
|
|
38
|
+
"./internal/server-config": "./src/server-config-store.ts",
|
|
39
|
+
"./internal/head": "./src/runtime/head.ts",
|
|
40
|
+
"./internal/default-layout": "./src/runtime/default-layout.tsx",
|
|
41
|
+
"./internal/checkout": "./src/pages/checkout.tsx",
|
|
42
|
+
"./internal/checkout-session": "./src/pages/checkout.$checkoutId.tsx",
|
|
43
|
+
"./internal/thank-you": "./src/pages/thank-you.tsx",
|
|
44
|
+
"./package.json": "./package.json"
|
|
45
|
+
},
|
|
20
46
|
"files": [
|
|
21
|
-
"
|
|
47
|
+
"bin",
|
|
48
|
+
"src",
|
|
49
|
+
"templates",
|
|
22
50
|
"README.md"
|
|
23
51
|
],
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
"
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
"
|
|
50
|
-
|
|
51
|
-
"import": "./dist/nextjs/api.js"
|
|
52
|
-
},
|
|
53
|
-
"./nextjs/css": "./dist/nextjs/globals.css",
|
|
54
|
-
"./nextjs/postcss": "./dist/postcss.config.mjs",
|
|
55
|
-
"./tanstack": {
|
|
56
|
-
"types": "./dist/tanstack.d.ts",
|
|
57
|
-
"react-server": {
|
|
58
|
-
"types": "./dist/tanstack.server.d.ts",
|
|
59
|
-
"import": "./dist/tanstack.server.js"
|
|
60
|
-
},
|
|
61
|
-
"import": "./dist/tanstack.js"
|
|
62
|
-
},
|
|
63
|
-
"./tanstack/api": {
|
|
64
|
-
"types": "./dist/tanstack/api.d.ts",
|
|
65
|
-
"import": "./dist/tanstack/api.js"
|
|
66
|
-
}
|
|
52
|
+
"sideEffects": [
|
|
53
|
+
"**/*.css"
|
|
54
|
+
],
|
|
55
|
+
"publishConfig": {
|
|
56
|
+
"access": "public",
|
|
57
|
+
"registry": "https://registry.npmjs.org/"
|
|
58
|
+
},
|
|
59
|
+
"dependencies": {
|
|
60
|
+
"@cloudflare/vite-plugin": "^1.44.0",
|
|
61
|
+
"@tailwindcss/vite": "^4.3.0",
|
|
62
|
+
"@tanstack/react-query": "^5.101.0",
|
|
63
|
+
"@tanstack/react-router": "^1.170.0",
|
|
64
|
+
"@tanstack/react-start": "^1.168.0",
|
|
65
|
+
"@vitejs/plugin-react": "^6.0.0",
|
|
66
|
+
"better-auth": "^1.6.23",
|
|
67
|
+
"class-variance-authority": "^0.7.1",
|
|
68
|
+
"clsx": "^2.1.1",
|
|
69
|
+
"embla-carousel-autoplay": "^8.6.0",
|
|
70
|
+
"embla-carousel-fade": "^8.6.0",
|
|
71
|
+
"embla-carousel-react": "^8.6.0",
|
|
72
|
+
"lucide-react": "^0.510.0",
|
|
73
|
+
"tailwind-merge": "^3.2.0",
|
|
74
|
+
"tailwindcss": "^4.3.0",
|
|
75
|
+
"vite": "^8.1.0",
|
|
76
|
+
"wrangler": "^4.100.0",
|
|
77
|
+
"zod": "^3.25.0",
|
|
78
|
+
"@ecomiq/core": "^0.1.1"
|
|
67
79
|
},
|
|
68
80
|
"peerDependencies": {
|
|
69
81
|
"react": "^19.0.0",
|
|
70
|
-
"react-dom": "^19.0.0"
|
|
71
|
-
"next": "^15.3.0",
|
|
72
|
-
"@tanstack/react-start": "^1.0.0"
|
|
82
|
+
"react-dom": "^19.0.0"
|
|
73
83
|
},
|
|
74
|
-
"
|
|
75
|
-
"
|
|
76
|
-
|
|
77
|
-
},
|
|
78
|
-
"@tanstack/react-start": {
|
|
79
|
-
"optional": true
|
|
80
|
-
}
|
|
84
|
+
"devDependencies": {
|
|
85
|
+
"@types/react": "^19.1.0",
|
|
86
|
+
"@types/react-dom": "^19.1.0"
|
|
81
87
|
},
|
|
82
|
-
"
|
|
83
|
-
|
|
84
|
-
"@hookform/resolvers": "^5.2.2",
|
|
85
|
-
"@tanstack/react-query": "^5.66.9",
|
|
86
|
-
"axios": "^1.8.2",
|
|
87
|
-
"better-auth": "^1.6.0",
|
|
88
|
-
"canvas-confetti": "^1.9.3",
|
|
89
|
-
"embla-carousel": "8.5.1",
|
|
90
|
-
"embla-carousel-autoplay": "8.5.1",
|
|
91
|
-
"embla-carousel-fade": "8.5.1",
|
|
92
|
-
"embla-carousel-react": "8.5.1",
|
|
93
|
-
"lucide-react": "^0.475.0",
|
|
94
|
-
"next-themes": "^0.4.4",
|
|
95
|
-
"react-hook-form": "^7.56.1",
|
|
96
|
-
"sonner": "^2.0.6",
|
|
97
|
-
"tailwindcss": "^4.0.12",
|
|
98
|
-
"zod": "^3.25.62",
|
|
99
|
-
"zustand": "^5.0.3"
|
|
100
|
-
}
|
|
101
|
-
}
|
|
88
|
+
"scripts": {}
|
|
89
|
+
}
|
package/src/api-proxy.ts
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Proxy HTTP hacia la API storefront de Ecomiq.
|
|
3
|
+
* /api/settings → {baseUrl}/v1/settings (con x-Store-Domain / x-Store-Id).
|
|
4
|
+
*/
|
|
5
|
+
import { resolveApiBaseUrl } from '@ecomiq/core'
|
|
6
|
+
import { STOREFRONT_ECOMIQ_OAUTH_COOKIE_NAME } from './auth/ecomiq-oauth-password-constants'
|
|
7
|
+
import { getStorefrontClient } from './client'
|
|
8
|
+
import { getServerConfig } from './server-config-store'
|
|
9
|
+
const ALLOWED_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']
|
|
10
|
+
|
|
11
|
+
const INCOMING_HEADER_ALLOWLIST = new Set([
|
|
12
|
+
'accept',
|
|
13
|
+
'accept-language',
|
|
14
|
+
'content-type',
|
|
15
|
+
])
|
|
16
|
+
|
|
17
|
+
const RESPONSE_HEADER_ALLOWLIST = new Set([
|
|
18
|
+
'cache-control',
|
|
19
|
+
'content-type',
|
|
20
|
+
'etag',
|
|
21
|
+
'last-modified',
|
|
22
|
+
'retry-after',
|
|
23
|
+
'vary',
|
|
24
|
+
])
|
|
25
|
+
|
|
26
|
+
const FORBIDDEN_SERVER_HEADERS = new Set([
|
|
27
|
+
'connection',
|
|
28
|
+
'keep-alive',
|
|
29
|
+
'proxy-authenticate',
|
|
30
|
+
'proxy-authorization',
|
|
31
|
+
'te',
|
|
32
|
+
'trailers',
|
|
33
|
+
'transfer-encoding',
|
|
34
|
+
'upgrade',
|
|
35
|
+
'host',
|
|
36
|
+
'content-length',
|
|
37
|
+
'cookie',
|
|
38
|
+
'cookie2',
|
|
39
|
+
'set-cookie',
|
|
40
|
+
'forwarded',
|
|
41
|
+
'x-forwarded-for',
|
|
42
|
+
'x-forwarded-host',
|
|
43
|
+
'x-forwarded-proto',
|
|
44
|
+
'x-real-ip',
|
|
45
|
+
])
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Reenvía un Request del store (`/api/...`) al storefront (`/v1/...`).
|
|
49
|
+
* Settings usa `x-Store-Domain`; el resto usa `x-Store-Id` (vía cache de settings).
|
|
50
|
+
*/
|
|
51
|
+
export async function proxyStorefrontRequest(
|
|
52
|
+
request: Request,
|
|
53
|
+
splat: string | undefined,
|
|
54
|
+
): Promise<Response> {
|
|
55
|
+
const method = request.method.toUpperCase()
|
|
56
|
+
if (!ALLOWED_METHODS.includes(method)) {
|
|
57
|
+
return Response.json(
|
|
58
|
+
{ error: 'Method not allowed', status: 405 },
|
|
59
|
+
{
|
|
60
|
+
status: 405,
|
|
61
|
+
headers: { allow: ALLOWED_METHODS.join(', ') },
|
|
62
|
+
},
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const config = getServerConfig()
|
|
67
|
+
const base = new URL(resolveApiBaseUrl(config.api?.baseUrl, config.api?.version))
|
|
68
|
+
const rawPath = splat ?? ''
|
|
69
|
+
let path = rawPath
|
|
70
|
+
let invalidPath =
|
|
71
|
+
/^[a-z][a-z\d+.-]*:/i.test(rawPath) ||
|
|
72
|
+
rawPath.startsWith('//') ||
|
|
73
|
+
rawPath.includes('\\') ||
|
|
74
|
+
rawPath.includes('?') ||
|
|
75
|
+
rawPath.includes('#') ||
|
|
76
|
+
/%(?:2f|5c)/i.test(rawPath)
|
|
77
|
+
|
|
78
|
+
if (path.startsWith('/')) path = path.slice(1)
|
|
79
|
+
for (const rawSegment of path.split('/')) {
|
|
80
|
+
let decoded = rawSegment
|
|
81
|
+
try {
|
|
82
|
+
for (let pass = 0; pass < 3; pass += 1) {
|
|
83
|
+
const next = decodeURIComponent(decoded)
|
|
84
|
+
if (next === decoded) break
|
|
85
|
+
decoded = next
|
|
86
|
+
}
|
|
87
|
+
} catch {
|
|
88
|
+
invalidPath = true
|
|
89
|
+
}
|
|
90
|
+
if (
|
|
91
|
+
decoded === '.' ||
|
|
92
|
+
decoded === '..' ||
|
|
93
|
+
decoded.includes('/') ||
|
|
94
|
+
decoded.includes('\\') ||
|
|
95
|
+
/[\u0000-\u001f\u007f]/.test(decoded)
|
|
96
|
+
) {
|
|
97
|
+
invalidPath = true
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (invalidPath) {
|
|
102
|
+
return Response.json(
|
|
103
|
+
{ error: 'Invalid proxy path', status: 400 },
|
|
104
|
+
{ status: 400 },
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const apiPrefix = base.pathname.endsWith('/') ? base.pathname : `${base.pathname}/`
|
|
109
|
+
const target = new URL(base)
|
|
110
|
+
target.pathname = `${apiPrefix}${path}`
|
|
111
|
+
const incoming = new URL(request.url)
|
|
112
|
+
target.search = incoming.search
|
|
113
|
+
|
|
114
|
+
if (target.origin !== base.origin || !target.pathname.startsWith(apiPrefix)) {
|
|
115
|
+
return Response.json(
|
|
116
|
+
{ error: 'Invalid proxy path', status: 400 },
|
|
117
|
+
{ status: 400 },
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const headers = new Headers()
|
|
122
|
+
for (const [key, value] of request.headers) {
|
|
123
|
+
if (INCOMING_HEADER_ALLOWLIST.has(key.toLowerCase())) headers.set(key, value)
|
|
124
|
+
}
|
|
125
|
+
for (const [key, value] of Object.entries(config.api?.headers ?? {})) {
|
|
126
|
+
const lower = key.toLowerCase()
|
|
127
|
+
if (FORBIDDEN_SERVER_HEADERS.has(lower) || lower.startsWith('cf-')) continue
|
|
128
|
+
headers.set(key, value)
|
|
129
|
+
}
|
|
130
|
+
headers.set('accept', headers.get('accept') ?? 'application/json')
|
|
131
|
+
|
|
132
|
+
const cookieHeader = request.headers.get('cookie')
|
|
133
|
+
if (cookieHeader) {
|
|
134
|
+
for (const part of cookieHeader.split(';')) {
|
|
135
|
+
const [rawName, ...rawValue] = part.trim().split('=')
|
|
136
|
+
if (rawName !== STOREFRONT_ECOMIQ_OAUTH_COOKIE_NAME) continue
|
|
137
|
+
try {
|
|
138
|
+
const accessToken = decodeURIComponent(rawValue.join('=')).trim()
|
|
139
|
+
if (accessToken) headers.set('authorization', `Bearer ${accessToken}`)
|
|
140
|
+
} catch {}
|
|
141
|
+
break
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const isSettings = path === 'settings' || path.startsWith('settings/')
|
|
146
|
+
if (isSettings) {
|
|
147
|
+
headers.delete('x-Store-Id')
|
|
148
|
+
headers.set('x-Store-Domain', config.slug)
|
|
149
|
+
} else {
|
|
150
|
+
try {
|
|
151
|
+
const storeId = await getStorefrontClient().getStoreId()
|
|
152
|
+
headers.set('x-Store-Id', storeId)
|
|
153
|
+
} catch {
|
|
154
|
+
return Response.json(
|
|
155
|
+
{ error: 'Bad gateway', status: 502 },
|
|
156
|
+
{ status: 502 },
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const body =
|
|
162
|
+
method === 'GET' || method === 'HEAD' ? undefined : await request.arrayBuffer()
|
|
163
|
+
|
|
164
|
+
let upstream: Response
|
|
165
|
+
try {
|
|
166
|
+
upstream = await fetch(target.toString(), { method, headers, body })
|
|
167
|
+
} catch {
|
|
168
|
+
return Response.json(
|
|
169
|
+
{ error: 'Bad gateway', status: 502 },
|
|
170
|
+
{ status: 502 },
|
|
171
|
+
)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const responseHeaders = new Headers()
|
|
175
|
+
for (const header of RESPONSE_HEADER_ALLOWLIST) {
|
|
176
|
+
const value = upstream.headers.get(header)
|
|
177
|
+
if (value) responseHeaders.set(header, value)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (upstream.status === 204 || upstream.status === 205 || upstream.status === 304) {
|
|
181
|
+
responseHeaders.delete('content-type')
|
|
182
|
+
return new Response(null, {
|
|
183
|
+
status: upstream.status,
|
|
184
|
+
statusText: upstream.statusText,
|
|
185
|
+
headers: responseHeaders,
|
|
186
|
+
})
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return new Response(upstream.body, {
|
|
190
|
+
status: upstream.status,
|
|
191
|
+
statusText: upstream.statusText,
|
|
192
|
+
headers: responseHeaders,
|
|
193
|
+
})
|
|
194
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { betterAuth } from 'better-auth'
|
|
2
|
+
import type { BetterAuthPlugin } from 'better-auth'
|
|
3
|
+
import { tanstackStartCookies } from 'better-auth/tanstack-start'
|
|
4
|
+
import { ecomiqOAuthPassword } from './ecomiq-oauth-password-plugin'
|
|
5
|
+
import { readProcessEnv } from './env'
|
|
6
|
+
|
|
7
|
+
export type CreateStorefrontAuthOptions = {
|
|
8
|
+
secret?: string
|
|
9
|
+
baseURL?: string
|
|
10
|
+
/** Opciones adicionales pasadas a `betterAuth` (p. ej. `database`). */
|
|
11
|
+
[key: string]: unknown
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Instancia Better Auth del storefront (servidor).
|
|
16
|
+
* Usa el plugin `ecomiqOAuthPassword` + cookies de TanStack Start.
|
|
17
|
+
*
|
|
18
|
+
* Si no pasas `secret`/`baseURL`, lee `BETTER_AUTH_SECRET` y `BETTER_AUTH_URL`.
|
|
19
|
+
*/
|
|
20
|
+
export function createStorefrontAuth(options: CreateStorefrontAuthOptions = {}) {
|
|
21
|
+
const { secret, baseURL, advanced, ...rest } = options
|
|
22
|
+
return betterAuth({
|
|
23
|
+
...rest,
|
|
24
|
+
secret: (secret as string | undefined) ?? readProcessEnv('BETTER_AUTH_SECRET'),
|
|
25
|
+
baseURL: (baseURL as string | undefined) ?? readProcessEnv('BETTER_AUTH_URL'),
|
|
26
|
+
advanced: {
|
|
27
|
+
disableCSRFCheck: true,
|
|
28
|
+
...(typeof advanced === 'object' && advanced !== null ? advanced : {}),
|
|
29
|
+
},
|
|
30
|
+
plugins: [
|
|
31
|
+
ecomiqOAuthPassword() as BetterAuthPlugin,
|
|
32
|
+
tanstackStartCookies(),
|
|
33
|
+
],
|
|
34
|
+
})
|
|
35
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { BetterAuthClientPlugin } from 'better-auth/client'
|
|
2
|
+
import { PLUGIN_ID } from './ecomiq-oauth-password-constants'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Cliente Better Auth para el plugin `ecomiqOAuthPassword` (login `/v1/customer/login`).
|
|
6
|
+
* Tras añadirlo a `createAuthClient`, refresca la sesión al llamar al sign-in.
|
|
7
|
+
*
|
|
8
|
+
* Ejemplo:
|
|
9
|
+
* `authClient.$fetch("/ecomiq-oauth/sign-in", { method: "POST", headers, body: { email, password } })`
|
|
10
|
+
*
|
|
11
|
+
* Importante: no anotar el retorno como `BetterAuthClientPlugin` (rompe la inferencia de sesión).
|
|
12
|
+
*/
|
|
13
|
+
export function ecomiqOAuthPasswordClient() {
|
|
14
|
+
return {
|
|
15
|
+
id: PLUGIN_ID,
|
|
16
|
+
pathMethods: {
|
|
17
|
+
'/ecomiq-oauth/sign-in': 'POST',
|
|
18
|
+
},
|
|
19
|
+
atomListeners: [
|
|
20
|
+
{
|
|
21
|
+
matcher: (path: string) => path === '/ecomiq-oauth/sign-in',
|
|
22
|
+
signal: '$sessionSignal' as const,
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
} satisfies BetterAuthClientPlugin
|
|
26
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { DEFAULT_API_BASE_URL, DEFAULT_API_VERSION } from '@ecomiq/core'
|
|
2
|
+
|
|
3
|
+
/** Id del plugin servidor/cliente Better Auth (debe coincidir en ambos lados). */
|
|
4
|
+
export const PLUGIN_ID = 'ecomiq-oauth-password'
|
|
5
|
+
|
|
6
|
+
/** Base storefront (override con `STOREFRONT_API_URL`). */
|
|
7
|
+
export const DEFAULT_STOREFRONT_API_URL = DEFAULT_API_BASE_URL
|
|
8
|
+
|
|
9
|
+
/** Prefijo de versión en storefront API (`/v1/...`). */
|
|
10
|
+
export const STOREFRONT_API_PATH_PREFIX = DEFAULT_API_VERSION
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Cookie HttpOnly con el bearer de customer login.
|
|
14
|
+
* Debe coincidir con el nombre que lee el proxy BFF (`api-proxy`).
|
|
15
|
+
*/
|
|
16
|
+
export const STOREFRONT_ECOMIQ_OAUTH_COOKIE_NAME = 'ecomiq_sf_oauth_at'
|
|
17
|
+
|
|
18
|
+
/** Path de la cookie (debe llegar a `/api/*` del proxy). */
|
|
19
|
+
export const STOREFRONT_ECOMIQ_OAUTH_COOKIE_PATH = '/api'
|
|
20
|
+
|
|
21
|
+
/** Headers de identidad tipados para login storefront. */
|
|
22
|
+
export const STOREFRONT_HEADERS = {
|
|
23
|
+
identityScope: 'x-Identity-Scope',
|
|
24
|
+
tenantId: 'x-Tenant-Id',
|
|
25
|
+
storeId: 'x-Store-Id',
|
|
26
|
+
} as const
|