@weaverse/next 0.1.0-alpha.9 → 1.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.
- package/README.md +161 -73
- package/dist/{chunk-6PMZ77RA.mjs → chunk-OSC5HR5V.mjs} +117 -8
- package/dist/chunk-OSC5HR5V.mjs.map +1 -0
- package/dist/client-boundary.js +1425 -0
- package/dist/client-boundary.js.map +1 -0
- package/dist/client-boundary.mjs +1395 -0
- package/dist/client-boundary.mjs.map +1 -0
- package/dist/index.js +292 -1003
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +90 -966
- package/dist/index.mjs.map +1 -1
- package/dist/server.js +321 -91
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +205 -28
- package/dist/server.mjs.map +1 -1
- package/dist/types/client-boundary.d.ts +25 -0
- package/dist/types/client-boundary.d.ts.map +1 -0
- package/dist/types/client.d.ts +16 -0
- package/dist/types/client.d.ts.map +1 -0
- package/dist/types/hooks.d.ts +20 -0
- package/dist/types/hooks.d.ts.map +1 -0
- package/dist/types/index.d.ts +33 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/item.d.ts +60 -0
- package/dist/types/item.d.ts.map +1 -0
- package/dist/types/loader.d.ts +25 -0
- package/dist/types/loader.d.ts.map +1 -0
- package/dist/types/main.d.ts +11 -0
- package/dist/types/main.d.ts.map +1 -0
- package/dist/types/provider.d.ts +82 -0
- package/dist/types/provider.d.ts.map +1 -0
- package/dist/types/registry.d.ts +15 -0
- package/dist/types/registry.d.ts.map +1 -0
- package/dist/types/renderer.d.ts +21 -0
- package/dist/types/renderer.d.ts.map +1 -0
- package/dist/types/request-info.d.ts +19 -0
- package/dist/types/request-info.d.ts.map +1 -0
- package/dist/types/revalidate-item.d.ts +82 -0
- package/dist/types/revalidate-item.d.ts.map +1 -0
- package/dist/types/root-provider.d.ts +62 -0
- package/dist/types/root-provider.d.ts.map +1 -0
- package/dist/types/runtime.d.ts +113 -0
- package/dist/types/runtime.d.ts.map +1 -0
- package/dist/types/seo.d.ts +55 -0
- package/dist/types/seo.d.ts.map +1 -0
- package/dist/types/server/configs.d.ts +27 -0
- package/dist/types/server/configs.d.ts.map +1 -0
- package/dist/types/server/normalize-page-url.d.ts +19 -0
- package/dist/types/server/normalize-page-url.d.ts.map +1 -0
- package/dist/types/server/revalidate-handler.d.ts +55 -0
- package/dist/types/server/revalidate-handler.d.ts.map +1 -0
- package/dist/types/server/server-client.d.ts +21 -0
- package/dist/types/server/server-client.d.ts.map +1 -0
- package/dist/types/server.d.ts +19 -0
- package/dist/types/server.d.ts.map +1 -0
- package/dist/types/studio-bridge.d.ts +16 -0
- package/dist/types/studio-bridge.d.ts.map +1 -0
- package/dist/types/studio-connect.d.ts +27 -0
- package/dist/types/studio-connect.d.ts.map +1 -0
- package/dist/types/studio-router.d.ts +50 -0
- package/dist/types/studio-router.d.ts.map +1 -0
- package/dist/types/studio-script-src.d.ts +12 -0
- package/dist/types/studio-script-src.d.ts.map +1 -0
- package/dist/types/theme-settings-store.d.ts +18 -0
- package/dist/types/theme-settings-store.d.ts.map +1 -0
- package/dist/types/translate.d.ts +56 -0
- package/dist/types/translate.d.ts.map +1 -0
- package/dist/types/translation-context.d.ts +109 -0
- package/dist/types/translation-context.d.ts.map +1 -0
- package/dist/types/translation-store.d.ts +44 -0
- package/dist/types/translation-store.d.ts.map +1 -0
- package/dist/types/types.d.ts +665 -0
- package/dist/types/types.d.ts.map +1 -0
- package/dist/types/use-weaverse-next-studio.d.ts +34 -0
- package/dist/types/use-weaverse-next-studio.d.ts.map +1 -0
- package/dist/types/utils.d.ts +22 -0
- package/dist/types/utils.d.ts.map +1 -0
- package/package.json +12 -27
- package/src/client-boundary.ts +50 -0
- package/src/client.ts +102 -0
- package/src/hooks.ts +50 -0
- package/src/index.ts +143 -0
- package/src/item.ts +167 -0
- package/src/loader.ts +135 -0
- package/src/main.tsx +45 -0
- package/src/provider.tsx +223 -0
- package/src/registry.ts +63 -0
- package/src/renderer.tsx +100 -0
- package/src/request-info.ts +124 -0
- package/src/revalidate-item.ts +211 -0
- package/src/root-provider.tsx +180 -0
- package/src/runtime.ts +555 -0
- package/src/seo.ts +157 -0
- package/src/server/configs.ts +178 -0
- package/src/server/normalize-page-url.ts +73 -0
- package/src/server/revalidate-handler.ts +357 -0
- package/src/server/server-client.ts +728 -0
- package/src/server.ts +67 -0
- package/src/studio-bridge.tsx +82 -0
- package/src/studio-connect.tsx +88 -0
- package/src/studio-router.ts +85 -0
- package/src/studio-script-src.ts +128 -0
- package/src/theme-settings-store.ts +47 -0
- package/src/translate.ts +125 -0
- package/src/translation-context.tsx +182 -0
- package/src/translation-store.ts +69 -0
- package/src/types.ts +753 -0
- package/src/use-weaverse-next-studio.tsx +106 -0
- package/src/utils.ts +49 -0
- package/dist/chunk-6PMZ77RA.mjs.map +0 -1
- package/dist/index.d.mts +0 -580
- package/dist/index.d.ts +0 -580
- package/dist/seo-Dos9t22_.d.mts +0 -477
- package/dist/seo-Dos9t22_.d.ts +0 -477
- package/dist/server.d.mts +0 -99
- package/dist/server.d.ts +0 -99
package/src/seo.ts
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import type { PageSEOData } from '@weaverse/schema'
|
|
2
|
+
import type { Metadata } from 'next'
|
|
3
|
+
import type { WeaverseNextLoaderData, WeaverseNextPageData } from './types'
|
|
4
|
+
|
|
5
|
+
/** Object containing an optional Weaverse page for SEO extraction. */
|
|
6
|
+
export interface WeaverseNextSeoPageContainer {
|
|
7
|
+
/** Page whose published SEO fields should be formatted. */
|
|
8
|
+
page?: WeaverseNextPageData | null
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Input accepted by {@link getWeaverseNextSeoMetadata}. */
|
|
12
|
+
export type WeaverseNextSeoInput =
|
|
13
|
+
| WeaverseNextLoaderData
|
|
14
|
+
| WeaverseNextPageData
|
|
15
|
+
| WeaverseNextSeoPageContainer
|
|
16
|
+
| null
|
|
17
|
+
| undefined
|
|
18
|
+
|
|
19
|
+
/** Open Graph metadata accepted by Next.js's `Metadata` API. */
|
|
20
|
+
export type NextOpenGraph = NonNullable<Metadata['openGraph']>
|
|
21
|
+
|
|
22
|
+
/** Twitter card metadata accepted by Next.js's `Metadata` API. */
|
|
23
|
+
export type NextTwitter = NonNullable<Metadata['twitter']>
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Weaverse page SEO expressed as a subset of Next's `Metadata`. Assignable to
|
|
27
|
+
* `Metadata` as-is: `const metadata: Metadata = getWeaverseNextSeoMetadata(data)`.
|
|
28
|
+
*/
|
|
29
|
+
export interface WeaverseNextSeoMetadata {
|
|
30
|
+
/** Canonical URL settings for Next's metadata API. */
|
|
31
|
+
alternates?: {
|
|
32
|
+
/** Preferred URL for search indexing. */
|
|
33
|
+
canonical?: string
|
|
34
|
+
}
|
|
35
|
+
/** Page meta description. */
|
|
36
|
+
description?: string
|
|
37
|
+
/** Page meta keywords. */
|
|
38
|
+
keywords?: string
|
|
39
|
+
/** Open Graph metadata normalized to Next's supported union. */
|
|
40
|
+
openGraph?: NextOpenGraph
|
|
41
|
+
/** Search crawler indexing and link-following directives. */
|
|
42
|
+
robots: {
|
|
43
|
+
/** Whether crawlers may follow links on the page. */
|
|
44
|
+
follow: boolean
|
|
45
|
+
/** Whether crawlers may index the page. */
|
|
46
|
+
index: boolean
|
|
47
|
+
}
|
|
48
|
+
/** Page title. */
|
|
49
|
+
title?: string
|
|
50
|
+
/** Twitter card metadata normalized for Next's metadata API. */
|
|
51
|
+
twitter?: NextTwitter
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function hasOpenGraphContent(seo: PageSEOData['openGraph']): boolean {
|
|
55
|
+
return Boolean(seo?.title || seo?.description || seo?.image || seo?.type)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function hasTwitterContent(seo: PageSEOData['twitter']): boolean {
|
|
59
|
+
return Boolean(seo?.title || seo?.description || seo?.image || seo?.cardType)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function normalizeTwitterCard(
|
|
63
|
+
cardType: NonNullable<PageSEOData['twitter']>['cardType']
|
|
64
|
+
): 'summary' | 'summary_large_image' {
|
|
65
|
+
return cardType === 'summary_large_image' ? 'summary_large_image' : 'summary'
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Map a Builder Open Graph type onto Next's `OpenGraphType` union. Every
|
|
70
|
+
* Builder value except `product` exists in Next's union and is preserved
|
|
71
|
+
* verbatim; `product` is not modeled by Next and makes `generateMetadata()`
|
|
72
|
+
* throw, so it degrades to `website` — the closest renderable equivalent.
|
|
73
|
+
*/
|
|
74
|
+
function normalizeOpenGraphType(
|
|
75
|
+
type: NonNullable<PageSEOData['openGraph']>['type']
|
|
76
|
+
): 'article' | 'profile' | 'video.other' | 'website' {
|
|
77
|
+
if (type === 'article' || type === 'profile' || type === 'video.other') {
|
|
78
|
+
return type
|
|
79
|
+
}
|
|
80
|
+
return 'website'
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Convert Builder SEO fields into metadata accepted by Next.js.
|
|
85
|
+
*
|
|
86
|
+
* Unsupported Open Graph product types and incomplete Twitter app/player
|
|
87
|
+
* cards are safely downgraded to renderable Next.js variants.
|
|
88
|
+
*/
|
|
89
|
+
export function formatWeaverseNextSeoMetadata(
|
|
90
|
+
seo: PageSEOData | null | undefined
|
|
91
|
+
): WeaverseNextSeoMetadata {
|
|
92
|
+
let index = seo?.robots?.index !== false
|
|
93
|
+
let follow = seo?.robots?.follow !== false
|
|
94
|
+
let metadata: WeaverseNextSeoMetadata = {
|
|
95
|
+
robots: { index, follow },
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (!seo) {
|
|
99
|
+
return metadata
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (seo.title) {
|
|
103
|
+
metadata.title = seo.title
|
|
104
|
+
}
|
|
105
|
+
if (seo.description) {
|
|
106
|
+
metadata.description = seo.description
|
|
107
|
+
}
|
|
108
|
+
if (seo.keywords) {
|
|
109
|
+
metadata.keywords = seo.keywords
|
|
110
|
+
}
|
|
111
|
+
if (seo.canonicalUrl) {
|
|
112
|
+
metadata.alternates = { canonical: seo.canonicalUrl }
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
let openGraph = seo.openGraph
|
|
116
|
+
if (hasOpenGraphContent(openGraph)) {
|
|
117
|
+
metadata.openGraph = {
|
|
118
|
+
title: openGraph?.title,
|
|
119
|
+
description: openGraph?.description,
|
|
120
|
+
images: openGraph?.image ? [openGraph.image] : undefined,
|
|
121
|
+
type: normalizeOpenGraphType(openGraph?.type),
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
let twitter = seo.twitter
|
|
126
|
+
if (hasTwitterContent(twitter)) {
|
|
127
|
+
// Builder does not collect the descriptors Next requires for `app` cards
|
|
128
|
+
// (Next throws when they are missing) or `player` cards (which render an
|
|
129
|
+
// empty, useless card without a player URL and dimensions). Downgrade both
|
|
130
|
+
// to `summary` rather than emitting metadata Next cannot render.
|
|
131
|
+
metadata.twitter = {
|
|
132
|
+
card: normalizeTwitterCard(twitter?.cardType),
|
|
133
|
+
title: twitter?.title,
|
|
134
|
+
description: twitter?.description,
|
|
135
|
+
images: twitter?.image ? [twitter.image] : undefined,
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return metadata
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Read page SEO from loader data or a page payload and format it for Next.js
|
|
144
|
+
* `generateMetadata`. Missing data returns indexable/followable robot defaults.
|
|
145
|
+
*/
|
|
146
|
+
export function getWeaverseNextSeoMetadata(
|
|
147
|
+
data: WeaverseNextSeoInput
|
|
148
|
+
): WeaverseNextSeoMetadata {
|
|
149
|
+
if (!data) {
|
|
150
|
+
return formatWeaverseNextSeoMetadata(null)
|
|
151
|
+
}
|
|
152
|
+
let page =
|
|
153
|
+
'items' in data && 'id' in data
|
|
154
|
+
? (data as WeaverseNextPageData)
|
|
155
|
+
: ((data as WeaverseNextSeoPageContainer).page ?? null)
|
|
156
|
+
return formatWeaverseNextSeoMetadata(page?.seo ?? null)
|
|
157
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
WeaverseNextBaseConfigs,
|
|
3
|
+
WeaverseNextRequestContext,
|
|
4
|
+
} from '../types'
|
|
5
|
+
|
|
6
|
+
const DEFAULT_WEAVERSE_HOST = 'https://studio.weaverse.io'
|
|
7
|
+
const DEFAULT_WEAVERSE_API_BASE = 'https://api.weaverse.io'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Weaverse-controlled apex domains the Studio bridge / data API may be served
|
|
11
|
+
* from (matched on the host itself and any subdomain). Kept in sync with
|
|
12
|
+
* Hydrogen's `isTrustedStudioHost`.
|
|
13
|
+
*/
|
|
14
|
+
const TRUSTED_STUDIO_DOMAINS = ['weaverse.io', 'weaverse.dev']
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Whether `host` is an origin we trust to resolve page/theme data. The
|
|
18
|
+
* `weaverseHost` query param is attacker-controllable, so a crafted
|
|
19
|
+
* `?weaverseHost=` must be rejected before it can point server-side fetches at
|
|
20
|
+
* an arbitrary origin (SSRF). Only `weaverse.io`/`weaverse.dev` and subdomains
|
|
21
|
+
* over https are trusted.
|
|
22
|
+
*/
|
|
23
|
+
function isTrustedStudioHost(host: string): boolean {
|
|
24
|
+
let parsed: URL
|
|
25
|
+
try {
|
|
26
|
+
parsed = new URL(host)
|
|
27
|
+
} catch {
|
|
28
|
+
return false
|
|
29
|
+
}
|
|
30
|
+
if (parsed.protocol !== 'https:') {
|
|
31
|
+
return false
|
|
32
|
+
}
|
|
33
|
+
let { hostname } = parsed
|
|
34
|
+
return TRUSTED_STUDIO_DOMAINS.some(
|
|
35
|
+
(domain) => hostname === domain || hostname.endsWith(`.${domain}`)
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function toUrl(value: string | URL): URL | null {
|
|
40
|
+
try {
|
|
41
|
+
return typeof value === 'string'
|
|
42
|
+
? new URL(value, 'http://localhost')
|
|
43
|
+
: value
|
|
44
|
+
} catch {
|
|
45
|
+
return null
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Extract the request search params from the explicit Next request context.
|
|
51
|
+
* Prefers `searchParams`, falls back to parsing `url`.
|
|
52
|
+
*/
|
|
53
|
+
export function getContextSearchParams(
|
|
54
|
+
context?: WeaverseNextRequestContext
|
|
55
|
+
): URLSearchParams {
|
|
56
|
+
if (context?.searchParams) {
|
|
57
|
+
return new URLSearchParams(context.searchParams)
|
|
58
|
+
}
|
|
59
|
+
if (context?.url) {
|
|
60
|
+
let url = toUrl(context.url)
|
|
61
|
+
if (url) {
|
|
62
|
+
return new URLSearchParams(url.searchParams)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return new URLSearchParams()
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Last value of a (possibly duplicated) query key. Matches Hydrogen's
|
|
70
|
+
* `getRequestQueries`, which assigns while iterating and so keeps the last
|
|
71
|
+
* occurrence — Studio appends its own control params, so a stale earlier value
|
|
72
|
+
* must not win.
|
|
73
|
+
*/
|
|
74
|
+
function lastParam(
|
|
75
|
+
searchParams: URLSearchParams,
|
|
76
|
+
key: string
|
|
77
|
+
): string | undefined {
|
|
78
|
+
let values = searchParams.getAll(key)
|
|
79
|
+
return values.length ? values.at(-1) : undefined
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function coerceBool(value: string | undefined): boolean {
|
|
83
|
+
return value === 'true'
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function readEnv(
|
|
87
|
+
env: Record<string, string | undefined> | undefined,
|
|
88
|
+
key: string
|
|
89
|
+
): string | undefined {
|
|
90
|
+
let fromConfig = env?.[key]
|
|
91
|
+
if (fromConfig !== undefined) {
|
|
92
|
+
return fromConfig
|
|
93
|
+
}
|
|
94
|
+
if (typeof process !== 'undefined' && process.env) {
|
|
95
|
+
return process.env[key]
|
|
96
|
+
}
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Overrides used while resolving request-scoped Weaverse configuration. */
|
|
101
|
+
export interface ResolveConfigsOptions {
|
|
102
|
+
/** Environment values; missing keys fall back to `process.env`. */
|
|
103
|
+
env?: Record<string, string | undefined>
|
|
104
|
+
/** Public data API base override. */
|
|
105
|
+
weaverseApiBase?: string
|
|
106
|
+
/** Studio origin override. */
|
|
107
|
+
weaverseHost?: string
|
|
108
|
+
/** Studio asset version override. */
|
|
109
|
+
weaverseVersion?: string
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Derive Weaverse configs from the request context and env, close to Hydrogen's
|
|
114
|
+
* `getWeaverseConfigs`. Resolves the trusted host, public API base, design /
|
|
115
|
+
* preview / revision flags, and public env — but never resolves the final
|
|
116
|
+
* `projectId` (that priority chain, which may call an async resolver, lives in
|
|
117
|
+
* the server client). Query/env projectId candidates are returned for the
|
|
118
|
+
* client to combine with function/string inputs.
|
|
119
|
+
*/
|
|
120
|
+
export function getWeaverseNextConfigs(
|
|
121
|
+
context?: WeaverseNextRequestContext,
|
|
122
|
+
options: ResolveConfigsOptions = {}
|
|
123
|
+
): WeaverseNextBaseConfigs {
|
|
124
|
+
let searchParams = getContextSearchParams(context)
|
|
125
|
+
let { env } = options
|
|
126
|
+
|
|
127
|
+
let queryHost = lastParam(searchParams, 'weaverseHost')
|
|
128
|
+
// A trusted query host is canonicalized to its origin before use, so a crafted
|
|
129
|
+
// `?weaverseHost=https://studio.weaverse.io/evil?x=1` can only ever resolve to
|
|
130
|
+
// `https://studio.weaverse.io` — never carrying an attacker path/query into the
|
|
131
|
+
// server-side API base.
|
|
132
|
+
let trustedUrlHost =
|
|
133
|
+
queryHost && isTrustedStudioHost(queryHost) ? new URL(queryHost).origin : ''
|
|
134
|
+
|
|
135
|
+
let configuredHost =
|
|
136
|
+
options.weaverseHost || readEnv(env, 'WEAVERSE_HOST') || ''
|
|
137
|
+
let weaverseHost = trustedUrlHost || configuredHost || DEFAULT_WEAVERSE_HOST
|
|
138
|
+
|
|
139
|
+
// Public data reads default to the edge proxy, but an explicit custom host
|
|
140
|
+
// (staging/self-hosted Weaverse) must stay the API base so those deployments
|
|
141
|
+
// don't fetch page/config data from the production proxy.
|
|
142
|
+
let weaverseApiBase =
|
|
143
|
+
trustedUrlHost ||
|
|
144
|
+
options.weaverseApiBase ||
|
|
145
|
+
readEnv(env, 'WEAVERSE_PUBLIC_API_BASE') ||
|
|
146
|
+
(configuredHost && configuredHost !== DEFAULT_WEAVERSE_HOST
|
|
147
|
+
? configuredHost
|
|
148
|
+
: DEFAULT_WEAVERSE_API_BASE)
|
|
149
|
+
|
|
150
|
+
let isRevisionPreview =
|
|
151
|
+
context?.isRevisionPreview ?? searchParams.has('__revisionId')
|
|
152
|
+
let isDesignMode =
|
|
153
|
+
context?.isDesignMode ?? coerceBool(lastParam(searchParams, 'isDesignMode'))
|
|
154
|
+
let isPreviewMode =
|
|
155
|
+
context?.isPreviewMode ??
|
|
156
|
+
coerceBool(lastParam(searchParams, 'isPreviewMode'))
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
queryProjectId: lastParam(searchParams, 'weaverseProjectId') || '',
|
|
160
|
+
envProjectId: readEnv(env, 'WEAVERSE_PROJECT_ID') || '',
|
|
161
|
+
weaverseHost,
|
|
162
|
+
weaverseApiBase,
|
|
163
|
+
weaverseVersion:
|
|
164
|
+
lastParam(searchParams, 'weaverseVersion') ||
|
|
165
|
+
options.weaverseVersion ||
|
|
166
|
+
'',
|
|
167
|
+
isDesignMode,
|
|
168
|
+
isPreviewMode,
|
|
169
|
+
isRevisionPreview,
|
|
170
|
+
sectionType:
|
|
171
|
+
context?.sectionType || lastParam(searchParams, 'sectionType') || '',
|
|
172
|
+
publicEnv: {
|
|
173
|
+
PUBLIC_STORE_DOMAIN: readEnv(env, 'PUBLIC_STORE_DOMAIN') || '',
|
|
174
|
+
PUBLIC_STOREFRONT_API_TOKEN:
|
|
175
|
+
readEnv(env, 'PUBLIC_STOREFRONT_API_TOKEN') || '',
|
|
176
|
+
},
|
|
177
|
+
}
|
|
178
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { withPathPrefix } from '../request-info'
|
|
2
|
+
import type { WeaverseNextRequestContext } from '../types'
|
|
3
|
+
|
|
4
|
+
// Query params that influence Weaverse page resolution on the Builder:
|
|
5
|
+
// revision previews, design-mode flags, design-mode draft item overrides,
|
|
6
|
+
// and `weaverse*` control params (host, version, project id). Everything
|
|
7
|
+
// else — utm_*, fbclid, gclid, storefront filters — is irrelevant to which
|
|
8
|
+
// page the Builder returns and would otherwise fragment any fetch cache keyed
|
|
9
|
+
// on the request body. `weaverseDraftItem` is the current draft item param;
|
|
10
|
+
// `__weaverseDraftItem` is kept as a legacy compatibility alias.
|
|
11
|
+
const KEPT_PARAM_RE =
|
|
12
|
+
/^(?:__revisionId|__weaverseDraftItem|isDesignMode|weaverse)/
|
|
13
|
+
const DATA_SUFFIX = '.data'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Normalize the storefront URL sent in the Builder page API request body.
|
|
17
|
+
*
|
|
18
|
+
* The Builder reads only the pathname (locale/type/handle fallback) and the
|
|
19
|
+
* Weaverse/Studio control params from this URL, so keep exactly those and drop
|
|
20
|
+
* tracking params. Params are sorted for a stable, cache-friendly string.
|
|
21
|
+
*
|
|
22
|
+
* Ported from `@weaverse/hydrogen`'s `normalizePageUrl`, but tolerant of a
|
|
23
|
+
* relative URL (Next route handlers may only know the pathname).
|
|
24
|
+
*/
|
|
25
|
+
export function normalizeNextPageUrl(url: string): string {
|
|
26
|
+
let parsed: URL
|
|
27
|
+
try {
|
|
28
|
+
parsed = new URL(url)
|
|
29
|
+
} catch {
|
|
30
|
+
try {
|
|
31
|
+
parsed = new URL(url, 'http://localhost')
|
|
32
|
+
} catch {
|
|
33
|
+
return url
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let kept = [...parsed.searchParams].filter(([key]) => KEPT_PARAM_RE.test(key))
|
|
38
|
+
kept.sort(([a], [b]) => a.localeCompare(b))
|
|
39
|
+
let search = new URLSearchParams(kept).toString()
|
|
40
|
+
|
|
41
|
+
let { pathname } = parsed
|
|
42
|
+
if (pathname.endsWith(DATA_SUFFIX)) {
|
|
43
|
+
pathname = pathname.slice(0, -DATA_SUFFIX.length)
|
|
44
|
+
}
|
|
45
|
+
return `${parsed.origin}${pathname}${search ? `?${search}` : ''}`
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Build an absolute-ish request URL string from the explicit request context,
|
|
50
|
+
* preferring `url`, then `pathname` + `searchParams`. Used as the input to
|
|
51
|
+
* {@link normalizeNextPageUrl}.
|
|
52
|
+
*/
|
|
53
|
+
export function resolveRequestUrl(
|
|
54
|
+
context?: WeaverseNextRequestContext
|
|
55
|
+
): string {
|
|
56
|
+
if (context?.url) {
|
|
57
|
+
let raw = String(context.url)
|
|
58
|
+
let url = new URL(raw, 'http://localhost')
|
|
59
|
+
let pathname = withPathPrefix(url.pathname, context.i18n)
|
|
60
|
+
// Unchanged when there is nothing to prefix, so a relative or bare-origin
|
|
61
|
+
// URL is passed on exactly as the app gave it.
|
|
62
|
+
if (pathname === url.pathname) {
|
|
63
|
+
return raw
|
|
64
|
+
}
|
|
65
|
+
url.pathname = pathname
|
|
66
|
+
return url.toString()
|
|
67
|
+
}
|
|
68
|
+
let pathname = withPathPrefix(context?.pathname || '/', context?.i18n)
|
|
69
|
+
let search = context?.searchParams
|
|
70
|
+
? new URLSearchParams(context.searchParams).toString()
|
|
71
|
+
: ''
|
|
72
|
+
return `http://localhost${pathname}${search ? `?${search}` : ''}`
|
|
73
|
+
}
|