@ecomiq/storefront 1.1.9 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/README.md +30 -415
  2. package/bin/ecomiq.mjs +165 -0
  3. package/package.json +72 -94
  4. package/src/api-proxy.ts +194 -0
  5. package/src/auth/create-storefront-auth.ts +35 -0
  6. package/src/auth/ecomiq-oauth-password-client.ts +26 -0
  7. package/src/auth/ecomiq-oauth-password-constants.ts +26 -0
  8. package/src/auth/ecomiq-oauth-password-plugin.ts +299 -0
  9. package/src/auth/ecomiq-oauth-password-types.ts +25 -0
  10. package/src/auth/ecomiq-storefront-sign-in.ts +47 -0
  11. package/src/auth/env.ts +7 -0
  12. package/src/cart-session.ts +241 -0
  13. package/src/client.ts +48 -0
  14. package/src/codegen.mjs +704 -0
  15. package/src/components/checkout/cart-sync.ts +143 -0
  16. package/src/components/checkout/checkout-external-update-banner.tsx +15 -0
  17. package/src/components/checkout/checkout-form-controls.tsx +139 -0
  18. package/src/components/checkout/checkout-line-items.tsx +43 -0
  19. package/src/components/checkout/checkout-order-summary.tsx +156 -0
  20. package/src/components/checkout/checkout-payment-section.tsx +215 -0
  21. package/src/components/checkout/checkout-session-view.tsx +240 -0
  22. package/src/components/checkout/checkout-shipping-address-section.tsx +266 -0
  23. package/src/components/checkout/checkout-shipping-section.tsx +90 -0
  24. package/src/components/checkout/checkout-states.tsx +73 -0
  25. package/src/components/checkout/form-schema.ts +229 -0
  26. package/src/components/checkout/format.ts +27 -0
  27. package/src/components/checkout/index.ts +51 -0
  28. package/src/components/checkout/types.ts +43 -0
  29. package/src/components/checkout/use-checkout-geo.ts +50 -0
  30. package/src/components/checkout/use-checkout-session.ts +583 -0
  31. package/src/components/hero.tsx +291 -0
  32. package/src/config.d.ts +69 -0
  33. package/src/config.mjs +165 -0
  34. package/src/index.ts +274 -0
  35. package/src/link.tsx +2 -0
  36. package/src/pages/checkout.$checkoutId.tsx +60 -0
  37. package/src/pages/checkout.tsx +31 -0
  38. package/src/pages/thank-you.tsx +53 -0
  39. package/src/providers/auth.tsx +69 -0
  40. package/src/providers/cart.tsx +275 -0
  41. package/src/providers/ecomiq.ts +137 -0
  42. package/src/providers/index.ts +26 -0
  43. package/src/public-config.ts +44 -0
  44. package/src/review-submission.ts +89 -0
  45. package/src/runtime/context.ts +1 -0
  46. package/src/runtime/default-layout.tsx +80 -0
  47. package/src/runtime/head.ts +126 -0
  48. package/src/runtime/page.tsx +82 -0
  49. package/src/runtime/postcss-browser-stub.mjs +5 -0
  50. package/src/server-config-store.ts +26 -0
  51. package/src/server.ts +279 -0
  52. package/src/styles.css +76 -0
  53. package/src/ui/badge.tsx +32 -0
  54. package/src/ui/button.tsx +31 -0
  55. package/src/ui/card.tsx +31 -0
  56. package/src/ui/carousel.tsx +218 -0
  57. package/src/ui/cn.ts +6 -0
  58. package/src/ui/index.ts +8 -0
  59. package/src/ui/separator.tsx +6 -0
  60. package/src/ui/skeleton.tsx +6 -0
  61. package/src/ui/storefront-error.tsx +49 -0
  62. package/src/utils.ts +36 -0
  63. package/src/vite-inline.mjs +135 -0
  64. package/templates/__root.tsx +77 -0
  65. package/templates/router.tsx +19 -0
  66. package/dist/chunk-64GLOWMJ.js +0 -216
  67. package/dist/chunk-64GLOWMJ.js.map +0 -1
  68. package/dist/chunk-AKD5IDEB.js +0 -177
  69. package/dist/chunk-AKD5IDEB.js.map +0 -1
  70. package/dist/chunk-EFEVJJWF.js +0 -1345
  71. package/dist/chunk-EFEVJJWF.js.map +0 -1
  72. package/dist/chunk-NAJQB5DN.js +0 -155
  73. package/dist/chunk-NAJQB5DN.js.map +0 -1
  74. package/dist/chunk-OABDGTBI.js +0 -253
  75. package/dist/chunk-OABDGTBI.js.map +0 -1
  76. package/dist/chunk-Q4U5OEUT.js +0 -21
  77. package/dist/chunk-Q4U5OEUT.js.map +0 -1
  78. package/dist/chunk-S7CPZ4UE.js +0 -6
  79. package/dist/chunk-S7CPZ4UE.js.map +0 -1
  80. package/dist/chunks/client.css +0 -984
  81. package/dist/chunks/client.css.map +0 -1
  82. package/dist/chunks/client.d.ts +0 -10759
  83. package/dist/chunks/client.js +0 -5398
  84. package/dist/chunks/client.js.map +0 -1
  85. package/dist/chunks/config.d.ts +0 -31
  86. package/dist/chunks/config.js +0 -5
  87. package/dist/chunks/config.js.map +0 -1
  88. package/dist/chunks/core-public.d.ts +0 -130
  89. package/dist/chunks/core-public.js +0 -92
  90. package/dist/chunks/core-public.js.map +0 -1
  91. package/dist/chunks/server-nextjs.d.ts +0 -33
  92. package/dist/chunks/server-nextjs.js +0 -59
  93. package/dist/chunks/server-nextjs.js.map +0 -1
  94. package/dist/chunks/server-tanstack.d.ts +0 -22
  95. package/dist/chunks/server-tanstack.js +0 -40
  96. package/dist/chunks/server-tanstack.js.map +0 -1
  97. package/dist/collection-DU7gREXS.d.ts +0 -4062
  98. package/dist/ecomiq-oauth-password-plugin-B_gMdGuc.d.ts +0 -3631
  99. package/dist/ecomiq-shell-ui-SWIFLQVM.js +0 -8
  100. package/dist/ecomiq-shell-ui-SWIFLQVM.js.map +0 -1
  101. package/dist/index.d.ts +0 -9
  102. package/dist/index.js +0 -1
  103. package/dist/index.js.map +0 -1
  104. package/dist/index.server.d.ts +0 -9
  105. package/dist/index.server.js +0 -8
  106. package/dist/index.server.js.map +0 -1
  107. package/dist/nextjs/api.d.ts +0 -45
  108. package/dist/nextjs/api.js +0 -32
  109. package/dist/nextjs/api.js.map +0 -1
  110. package/dist/nextjs/globals.css +0 -13
  111. package/dist/nextjs.d.ts +0 -2
  112. package/dist/nextjs.js +0 -2
  113. package/dist/nextjs.js.map +0 -1
  114. package/dist/nextjs.server.d.ts +0 -18
  115. package/dist/nextjs.server.js +0 -1
  116. package/dist/nextjs.server.js.map +0 -1
  117. package/dist/postcss.config.mjs +0 -6
  118. package/dist/react/checkout-page.css +0 -855
  119. package/dist/react/client.css +0 -984
  120. package/dist/react/payment-provider.css +0 -91
  121. package/dist/react/thank-you-page.css +0 -105
  122. package/dist/react.d.ts +0 -1
  123. package/dist/react.js +0 -2
  124. package/dist/react.js.map +0 -1
  125. package/dist/store-proxy-handler-CNzKlTFA.d.ts +0 -10
  126. package/dist/tanstack/api.d.ts +0 -41
  127. package/dist/tanstack/api.js +0 -30
  128. package/dist/tanstack/api.js.map +0 -1
  129. package/dist/tanstack.d.ts +0 -1
  130. package/dist/tanstack.js +0 -2
  131. package/dist/tanstack.js.map +0 -1
  132. package/dist/tanstack.server.d.ts +0 -18
  133. package/dist/tanstack.server.js +0 -1
  134. package/dist/tanstack.server.js.map +0 -1
package/package.json CHANGED
@@ -1,111 +1,89 @@
1
1
  {
2
2
  "name": "@ecomiq/storefront",
3
- "description": "SDK headless de storefront Ecomiq: adapters Next.js y TanStack Start sobre React (sin @ecomiq/ui)",
4
- "license": "MIT",
3
+ "version": "2.0.1",
4
+ "description": "Micro framework SSR de Ecomiq para storefronts. Páginas, datos y deploy sin configurar nada.",
5
5
  "type": "module",
6
- "publishConfig": {
7
- "access": "public"
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
- "nextjs",
14
- "tanstack-start",
15
- "react"
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
- "dist",
47
+ "bin",
48
+ "src",
49
+ "templates",
22
50
  "README.md"
23
51
  ],
24
- "exports": {
25
- ".": {
26
- "types": "./dist/index.d.ts",
27
- "react-server": {
28
- "types": "./dist/index.server.d.ts",
29
- "import": "./dist/index.server.js"
30
- },
31
- "import": "./dist/index.js"
32
- },
33
- "./react": {
34
- "types": "./dist/react.d.ts",
35
- "import": "./dist/react.js"
36
- },
37
- "./react/checkout-page.css": "./dist/react/checkout-page.css",
38
- "./react/payment-provider.css": "./dist/react/payment-provider.css",
39
- "./react/thank-you-page.css": "./dist/react/thank-you-page.css",
40
- "./react/client.css": "./dist/react/client.css",
41
- "./nextjs": {
42
- "types": "./dist/nextjs.d.ts",
43
- "react-server": {
44
- "types": "./dist/nextjs.server.d.ts",
45
- "import": "./dist/nextjs.server.js"
46
- },
47
- "import": "./dist/nextjs.js"
48
- },
49
- "./nextjs/api": {
50
- "types": "./dist/nextjs/api.d.ts",
51
- "import": "./dist/nextjs/api.js"
52
- },
53
- "./nextjs/server": {
54
- "types": "./dist/nextjs.server.d.ts",
55
- "import": "./dist/nextjs.server.js",
56
- "default": "./dist/nextjs.server.js"
57
- },
58
- "./nextjs/css": "./dist/nextjs/globals.css",
59
- "./nextjs/postcss": "./dist/postcss.config.mjs",
60
- "./tanstack": {
61
- "types": "./dist/tanstack.d.ts",
62
- "react-server": {
63
- "types": "./dist/tanstack.server.d.ts",
64
- "import": "./dist/tanstack.server.js"
65
- },
66
- "import": "./dist/tanstack.js"
67
- },
68
- "./tanstack/server": {
69
- "types": "./dist/tanstack.server.d.ts",
70
- "import": "./dist/tanstack.server.js",
71
- "default": "./dist/tanstack.server.js"
72
- },
73
- "./tanstack/api": {
74
- "types": "./dist/tanstack/api.d.ts",
75
- "import": "./dist/tanstack/api.js"
76
- }
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"
77
79
  },
78
80
  "peerDependencies": {
79
81
  "react": "^19.0.0",
80
- "react-dom": "^19.0.0",
81
- "next": "^15.3.0",
82
- "@tanstack/react-start": "^1.0.0"
82
+ "react-dom": "^19.0.0"
83
83
  },
84
- "peerDependenciesMeta": {
85
- "next": {
86
- "optional": true
87
- },
88
- "@tanstack/react-start": {
89
- "optional": true
90
- }
84
+ "devDependencies": {
85
+ "@types/react": "^19.1.0",
86
+ "@types/react-dom": "^19.1.0"
91
87
  },
92
- "version": "1.1.9",
93
- "dependencies": {
94
- "@hookform/resolvers": "^5.2.2",
95
- "@tanstack/react-query": "^5.66.9",
96
- "axios": "^1.8.2",
97
- "better-auth": "^1.6.16",
98
- "canvas-confetti": "^1.9.3",
99
- "embla-carousel": "8.5.1",
100
- "embla-carousel-autoplay": "8.5.1",
101
- "embla-carousel-fade": "8.5.1",
102
- "embla-carousel-react": "8.5.1",
103
- "lucide-react": "^0.475.0",
104
- "next-themes": "^0.4.4",
105
- "react-hook-form": "^7.56.1",
106
- "sonner": "^2.0.6",
107
- "tailwindcss": "^4.0.12",
108
- "zod": "^3.25.62",
109
- "zustand": "^5.0.3"
110
- }
111
- }
88
+ "scripts": {}
89
+ }
@@ -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