@salesforce/storefront-next-runtime 0.3.0-alpha.0 → 0.3.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/dist/apply-url-config.js +4 -4
- package/dist/apply-url-config.js.map +1 -1
- package/dist/config.d.ts +3 -3
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +5 -2
- package/dist/config.js.map +1 -1
- package/dist/design-react-core.d.ts +2 -2
- package/dist/events.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/load-config.js +2 -1
- package/dist/load-config.js.map +1 -1
- package/dist/routing.d.ts +1 -1
- package/dist/routing.js +1 -1
- package/dist/routing.js.map +1 -1
- package/dist/scapi.d.ts.map +1 -1
- package/dist/{multi-site.d.ts → site-context.d.ts} +21 -21
- package/dist/site-context.d.ts.map +1 -0
- package/dist/{multi-site.js → site-context.js} +32 -32
- package/dist/site-context.js.map +1 -0
- package/package.json +4 -4
- package/dist/multi-site.d.ts.map +0 -1
- package/dist/multi-site.js.map +0 -1
|
@@ -5,7 +5,7 @@ import * as react_router0 from "react-router";
|
|
|
5
5
|
import { Cookie, MiddlewareFunction, RouterContextProvider } from "react-router";
|
|
6
6
|
import { RouteConfigEntry } from "@react-router/dev/routes";
|
|
7
7
|
|
|
8
|
-
//#region src/
|
|
8
|
+
//#region src/site-context/types.d.ts
|
|
9
9
|
|
|
10
10
|
type Locale = Locale$1 & {
|
|
11
11
|
alias?: string;
|
|
@@ -15,17 +15,17 @@ type Site = Omit<Site$1, 'supportedLocales'> & {
|
|
|
15
15
|
alias?: string;
|
|
16
16
|
supportedLocales: Locale[];
|
|
17
17
|
};
|
|
18
|
-
type
|
|
18
|
+
type SiteContext = {
|
|
19
19
|
site: Site;
|
|
20
20
|
locale: Locale;
|
|
21
21
|
siteCookie: Cookie;
|
|
22
22
|
localeCookie: Cookie;
|
|
23
23
|
};
|
|
24
24
|
/**
|
|
25
|
-
* Configuration passed into the
|
|
25
|
+
* Configuration passed into the site context middleware
|
|
26
26
|
* Configured by the consumer
|
|
27
27
|
*/
|
|
28
|
-
type
|
|
28
|
+
type SiteConfig = {
|
|
29
29
|
sites: Site[];
|
|
30
30
|
defaultSiteId: string;
|
|
31
31
|
defaultLocale: string;
|
|
@@ -43,7 +43,7 @@ type DetectionConfig = {
|
|
|
43
43
|
caches?: Array<'cookie'>;
|
|
44
44
|
};
|
|
45
45
|
//#endregion
|
|
46
|
-
//#region src/
|
|
46
|
+
//#region src/site-context/site-context.d.ts
|
|
47
47
|
/**
|
|
48
48
|
* Provides the current site to the component tree.
|
|
49
49
|
* Follows the same pattern as CurrencyProvider.
|
|
@@ -63,9 +63,9 @@ declare function SiteProvider({
|
|
|
63
63
|
*/
|
|
64
64
|
declare function useSite(): Site | undefined;
|
|
65
65
|
//#endregion
|
|
66
|
-
//#region src/
|
|
66
|
+
//#region src/site-context/apply-url-config.d.ts
|
|
67
67
|
/**
|
|
68
|
-
* Applies
|
|
68
|
+
* Applies site context URL configuration to a set of route entries.
|
|
69
69
|
*
|
|
70
70
|
* Wraps non-excluded routes under a parent route with the configured URL prefix
|
|
71
71
|
* (e.g. `/:siteId/:localeId`), while keeping excluded routes (action/resource by default)
|
|
@@ -84,7 +84,7 @@ declare function applyUrlConfig(options: {
|
|
|
84
84
|
wrapperFile: string;
|
|
85
85
|
}): RouteConfigEntry[];
|
|
86
86
|
//#endregion
|
|
87
|
-
//#region src/
|
|
87
|
+
//#region src/site-context/build-url.d.ts
|
|
88
88
|
/**
|
|
89
89
|
* Resolves a prefix template by replacing parameter placeholders with values.
|
|
90
90
|
* ('/:siteId/:localeId', { siteId: 'global', localeId: 'en-GB' }) → '/global/en-GB'
|
|
@@ -113,9 +113,9 @@ declare function stripPathPrefix(pathname: string, prefixPattern: string): strin
|
|
|
113
113
|
*/
|
|
114
114
|
declare function sanitizePrefix(pathname: string, pathPrefix: string): string;
|
|
115
115
|
/**
|
|
116
|
-
* Builds a fully-qualified URL with
|
|
116
|
+
* Builds a fully-qualified URL with site context prefix and search params.
|
|
117
117
|
*
|
|
118
|
-
* Only keys defined in urlConfig.search are set by
|
|
118
|
+
* Only keys defined in urlConfig.search are set by site context. Any other query params
|
|
119
119
|
* already present on the `to` URL (including duplicate keys) are preserved as-is.
|
|
120
120
|
* e.g. to='/api/search?refine=color:blue&refine=size:M', search='?lng=:localeId'
|
|
121
121
|
* → '/api/search?refine=color:blue&refine=size:M&lng=en-GB'
|
|
@@ -134,10 +134,10 @@ declare function buildUrl({
|
|
|
134
134
|
params: Record<string, string>;
|
|
135
135
|
}): string;
|
|
136
136
|
//#endregion
|
|
137
|
-
//#region src/
|
|
138
|
-
declare const
|
|
137
|
+
//#region src/site-context/middleware.d.ts
|
|
138
|
+
declare const siteContext: react_router0.RouterContext<SiteContext | null>;
|
|
139
139
|
/**
|
|
140
|
-
* Helper function to get
|
|
140
|
+
* Helper function to get site context cookies from router context.
|
|
141
141
|
* Useful in server actions and loaders that need to read/set cookies.
|
|
142
142
|
*
|
|
143
143
|
* @param context - Router context provider
|
|
@@ -146,7 +146,7 @@ declare const multiSiteContext: react_router0.RouterContext<MultiSiteContext | n
|
|
|
146
146
|
* @example
|
|
147
147
|
* ```typescript
|
|
148
148
|
* export const action: ActionFunction = async ({ request, context }) => {
|
|
149
|
-
* const cookies =
|
|
149
|
+
* const cookies = getSiteContextCookies(context);
|
|
150
150
|
* if (cookies) {
|
|
151
151
|
* const cookieHeader = await cookies.localeCookie.serialize(locale);
|
|
152
152
|
* // ... use cookieHeader
|
|
@@ -154,26 +154,26 @@ declare const multiSiteContext: react_router0.RouterContext<MultiSiteContext | n
|
|
|
154
154
|
* };
|
|
155
155
|
* ```
|
|
156
156
|
*/
|
|
157
|
-
declare function
|
|
157
|
+
declare function getSiteContextCookies(context: Readonly<RouterContextProvider>): {
|
|
158
158
|
siteCookie: react_router0.Cookie;
|
|
159
159
|
localeCookie: react_router0.Cookie;
|
|
160
160
|
} | null;
|
|
161
161
|
/**
|
|
162
|
-
* Creates a
|
|
162
|
+
* Creates a site context middleware that resolves the current site from
|
|
163
163
|
* the request (path, cookie, header, query, or default) and stores the
|
|
164
164
|
* result in the router context.
|
|
165
165
|
*
|
|
166
166
|
* Does not import or read from app config context; the consumer supplies config.
|
|
167
167
|
*/
|
|
168
|
-
declare function
|
|
168
|
+
declare function createSiteContextMiddleware(config: SiteConfig): MiddlewareFunction<Response>;
|
|
169
169
|
//#endregion
|
|
170
|
-
//#region src/
|
|
170
|
+
//#region src/site-context/cookies.d.ts
|
|
171
171
|
/**
|
|
172
|
-
* WeakMap to pass resolved locale from
|
|
172
|
+
* WeakMap to pass resolved locale from site context middleware to i18next's findLocale.
|
|
173
173
|
* WeakMap allows garbage collection when requests are done.
|
|
174
174
|
* This is necessary because findLocale() only receives the Request object, not the router context.
|
|
175
175
|
*/
|
|
176
176
|
declare const requestToLocaleMap: WeakMap<Request, string>;
|
|
177
177
|
//#endregion
|
|
178
|
-
export { type DetectionConfig, type Locale, type
|
|
179
|
-
//# sourceMappingURL=
|
|
178
|
+
export { type DetectionConfig, type Locale, type Site, type SiteConfig, type SiteContext, SiteProvider, applyUrlConfig, buildUrl, createSiteContextMiddleware, getSiteContextCookies, requestToLocaleMap, resolvePrefix, sanitizePrefix, siteContext, stripPathPrefix, useSite };
|
|
179
|
+
//# sourceMappingURL=site-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"site-context.d.ts","names":[],"sources":["../src/site-context/types.ts","../src/site-context/site-context.tsx","../src/site-context/apply-url-config.ts","../src/site-context/build-url.ts","../src/site-context/middleware.ts","../src/site-context/cookies.ts"],"sourcesContent":[],"mappings":";;;;;;;;;AAiCgB,KAbJ,MAAA,GAAS,QAaL,GAAA;EACE,KAAA,CAAA,EAAA,MAAA;CAAM;AAOZ,KAjBA,IAAA,GAAO,IAiBG,CAjBE,MAiBF,EAAA,kBAAA,CAAA,GAAA;EACX,IAAA,CAAA,EAAA,MAAA;EAGe,KAAA,CAAA,EAAA,MAAA;EACE,gBAAA,EAnBN,MAmBM,EAAA;CAAe;AAc/B,KA9BA,WAAA,GA8Be;EAGf,IAAA,EAhCF,IAgCE;UA/BA;cACI;gBACE;ACPlB,CAAA;;;;;AAAoF,KDcxE,UAAA,GCdwE;EAAA,KAAA,EDezE,ICfyE,EAAA;EASpE,aAAO,EAAA,MAAA;;wBDSG;0BACE;AE0E5B,CAAA;ACnBA;AAmBgB,KH5DJ,eAAA,GG4DY,MAAA,GAAA,aAAA,GAAA,QAAA,GAAA,QAAA;AACpB,KH1DQ,eAAA,GG0DR;EACA,KAAA,EH1DO,eG0DP,EAAA;EACA,mBAAA,CAAA,EAAA,MAAA;EAGY,iBAAA,CAAA,EAAA,MAAA;EACJ,YAAA,CAAA,EAAA,MAAA;EAAM,YAAA,CAAA,EAAA,MAAA;WH1DL;;;;;;;AA5Bb;;;;AAK2C,iBCnB3B,YAAA,CDmB2B;EAAA,KAAA;EAAA;AAiB3C,CAjB2C,ECnBO,iBDmBP,CAAA;EAc/B,KAAA,ECjCiE,IDiCjE;AAGZ,CAAA,CAAA,CAAA,ECpCoF,kBAAA,CAAA,GAAA,CAAA,ODqCzE;;;;ACrCX;AAA+B,iBASf,OAAA,CAAA,CATe,EASJ,IATI,GAAA,SAAA;;;;AC6F/B;;;;;;;;AC/DA;AA0BA;AAkBA;AAmBA;;AAEI,iBDFY,cAAA,CCEZ,OAAA,EAAA;EACA,MAAA,EDFQ,gBCER,EAAA;EAGY,SAAA,CAAA,EDJA,GCIA;EACJ,WAAA,EAAA,MAAA;CAAM,CAAA,EDHd,gBCGc,EAAA;;;;;AF3FlB;;iBEqBgB,aAAA,yBAAsC;;AD+DtD;;;;;;;;AC/DA;AA0BA;AAkBA;AAmBA;;;AAGI,iBAxCY,eAAA,CAwCZ,QAAA,EAAA,MAAA,EAAA,aAAA,EAAA,MAAA,CAAA,EAAA,MAAA;;;;;;iBAtBY,cAAA;AC9EhB;AAsBA;;;;;;AAoEA;;;;;iBDOgB,QAAA;;;;;;cAMA;EEnFH,MAAA,EFoFD,MEpFC,CAAA,MAAmD,EAAA,MAAA,CAAA;;;;ALVhD,cIVH,WJUG,EIVQ,aAAA,CAAA,aJUR,CIVQ,WJUR,GAAA,IAAA,CAAA;;;AAQhB;;;;;AAmBA;AAGA;;;;ACpCA;;;;;;AAAoF,iBGkBpE,qBAAA,CHlBoE,OAAA,EGkBrC,QHlBqC,CGkB5B,qBHlB4B,CAAA,CAAA,EAAA;EASpE,UAAO,EGSgC,aAAA,CAAA,MHTxB;;;;ACoF/B;;;;;;iBEPgB,2BAAA,SAAoC,aAAa,mBAAmB;;;;;;;;AH7EpE,cIOH,kBJPkB,EIOA,OJPA,CIOA,OJPA,EAAA,MAAA,CAAA"}
|
|
@@ -3,7 +3,7 @@ import { createContext, useContext } from "react";
|
|
|
3
3
|
import { jsx } from "react/jsx-runtime";
|
|
4
4
|
import { createContext as createContext$1, createCookie } from "react-router";
|
|
5
5
|
|
|
6
|
-
//#region src/
|
|
6
|
+
//#region src/site-context/site-context.tsx
|
|
7
7
|
const SiteContext = createContext(void 0);
|
|
8
8
|
/**
|
|
9
9
|
* Provides the current site to the component tree.
|
|
@@ -27,7 +27,7 @@ function useSite() {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
//#endregion
|
|
30
|
-
//#region src/
|
|
30
|
+
//#region src/site-context/build-url.ts
|
|
31
31
|
/**
|
|
32
32
|
* Parses search config string into key-value pairs, preserving ':param' placeholders.
|
|
33
33
|
* '?lng=:localeId&site=:siteId' → { lng: ':localeId', site: ':siteId' }
|
|
@@ -108,9 +108,9 @@ function sanitizePrefix(pathname, pathPrefix) {
|
|
|
108
108
|
return pathname;
|
|
109
109
|
}
|
|
110
110
|
/**
|
|
111
|
-
* Builds a fully-qualified URL with
|
|
111
|
+
* Builds a fully-qualified URL with site context prefix and search params.
|
|
112
112
|
*
|
|
113
|
-
* Only keys defined in urlConfig.search are set by
|
|
113
|
+
* Only keys defined in urlConfig.search are set by site context. Any other query params
|
|
114
114
|
* already present on the `to` URL (including duplicate keys) are preserved as-is.
|
|
115
115
|
* e.g. to='/api/search?refine=color:blue&refine=size:M', search='?lng=:localeId'
|
|
116
116
|
* → '/api/search?refine=color:blue&refine=size:M&lng=en-GB'
|
|
@@ -138,7 +138,7 @@ function buildUrl({ to, urlConfig, params }) {
|
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
//#endregion
|
|
141
|
-
//#region src/
|
|
141
|
+
//#region src/site-context/utils.ts
|
|
142
142
|
/**
|
|
143
143
|
* Extract a string value from the URL path segment at the given index.
|
|
144
144
|
*/
|
|
@@ -159,7 +159,7 @@ async function readCookieFromRequest(request, cookie) {
|
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
//#endregion
|
|
162
|
-
//#region src/
|
|
162
|
+
//#region src/site-context/site-detection.ts
|
|
163
163
|
/**
|
|
164
164
|
* Detect site reference from cookie.
|
|
165
165
|
*/
|
|
@@ -200,7 +200,7 @@ async function resolveSite(request, settings) {
|
|
|
200
200
|
}
|
|
201
201
|
|
|
202
202
|
//#endregion
|
|
203
|
-
//#region src/
|
|
203
|
+
//#region src/site-context/configs.ts
|
|
204
204
|
/**
|
|
205
205
|
* Default site detection configuration
|
|
206
206
|
*/
|
|
@@ -235,9 +235,9 @@ const DEFAULT_LOCALE_DETECTION = {
|
|
|
235
235
|
};
|
|
236
236
|
|
|
237
237
|
//#endregion
|
|
238
|
-
//#region src/
|
|
238
|
+
//#region src/site-context/cookies.ts
|
|
239
239
|
/**
|
|
240
|
-
* Cookie options for
|
|
240
|
+
* Cookie options for site context cookies
|
|
241
241
|
*/
|
|
242
242
|
const COOKIE_OPTIONS = {
|
|
243
243
|
path: "/",
|
|
@@ -249,20 +249,20 @@ const COOKIE_OPTIONS = {
|
|
|
249
249
|
* Creates a cookie instance with the given name.
|
|
250
250
|
*
|
|
251
251
|
* @param name - Cookie name
|
|
252
|
-
* @returns Cookie instance configured with
|
|
252
|
+
* @returns Cookie instance configured with site context options
|
|
253
253
|
*/
|
|
254
|
-
function
|
|
254
|
+
function createSiteContextCookie(name) {
|
|
255
255
|
return createCookie(name, COOKIE_OPTIONS);
|
|
256
256
|
}
|
|
257
257
|
/**
|
|
258
|
-
* WeakMap to pass resolved locale from
|
|
258
|
+
* WeakMap to pass resolved locale from site context middleware to i18next's findLocale.
|
|
259
259
|
* WeakMap allows garbage collection when requests are done.
|
|
260
260
|
* This is necessary because findLocale() only receives the Request object, not the router context.
|
|
261
261
|
*/
|
|
262
262
|
const requestToLocaleMap = /* @__PURE__ */ new WeakMap();
|
|
263
263
|
|
|
264
264
|
//#endregion
|
|
265
|
-
//#region src/
|
|
265
|
+
//#region src/site-context/locale-detection.ts
|
|
266
266
|
/**
|
|
267
267
|
* Read locale from cookie.
|
|
268
268
|
*/
|
|
@@ -310,10 +310,10 @@ async function resolveLocale(request, settings, site) {
|
|
|
310
310
|
}
|
|
311
311
|
|
|
312
312
|
//#endregion
|
|
313
|
-
//#region src/
|
|
314
|
-
const
|
|
313
|
+
//#region src/site-context/middleware.ts
|
|
314
|
+
const siteContext = createContext$1(null);
|
|
315
315
|
/**
|
|
316
|
-
* Helper function to get
|
|
316
|
+
* Helper function to get site context cookies from router context.
|
|
317
317
|
* Useful in server actions and loaders that need to read/set cookies.
|
|
318
318
|
*
|
|
319
319
|
* @param context - Router context provider
|
|
@@ -322,7 +322,7 @@ const multiSiteContext = createContext$1(null);
|
|
|
322
322
|
* @example
|
|
323
323
|
* ```typescript
|
|
324
324
|
* export const action: ActionFunction = async ({ request, context }) => {
|
|
325
|
-
* const cookies =
|
|
325
|
+
* const cookies = getSiteContextCookies(context);
|
|
326
326
|
* if (cookies) {
|
|
327
327
|
* const cookieHeader = await cookies.localeCookie.serialize(locale);
|
|
328
328
|
* // ... use cookieHeader
|
|
@@ -330,12 +330,12 @@ const multiSiteContext = createContext$1(null);
|
|
|
330
330
|
* };
|
|
331
331
|
* ```
|
|
332
332
|
*/
|
|
333
|
-
function
|
|
334
|
-
const
|
|
335
|
-
if (!
|
|
333
|
+
function getSiteContextCookies(context) {
|
|
334
|
+
const siteCtx = context.get(siteContext);
|
|
335
|
+
if (!siteCtx) return null;
|
|
336
336
|
return {
|
|
337
|
-
siteCookie:
|
|
338
|
-
localeCookie:
|
|
337
|
+
siteCookie: siteCtx.siteCookie,
|
|
338
|
+
localeCookie: siteCtx.localeCookie
|
|
339
339
|
};
|
|
340
340
|
}
|
|
341
341
|
/**
|
|
@@ -346,7 +346,7 @@ function getMultiSiteCookies(context) {
|
|
|
346
346
|
*
|
|
347
347
|
* @param request - Incoming request
|
|
348
348
|
* @param response - Response from next()
|
|
349
|
-
* @param settings -
|
|
349
|
+
* @param settings - Site context settings with cookie instances and detection config
|
|
350
350
|
* @param site - Resolved site for this request
|
|
351
351
|
* @param locale - Resolved locale for this request
|
|
352
352
|
* @returns Object with shouldSetSiteCookie and shouldSetLocaleCookie booleans
|
|
@@ -369,13 +369,13 @@ async function shouldSetCookies(request, response, settings, site, locale) {
|
|
|
369
369
|
};
|
|
370
370
|
}
|
|
371
371
|
/**
|
|
372
|
-
* Creates a
|
|
372
|
+
* Creates a site context middleware that resolves the current site from
|
|
373
373
|
* the request (path, cookie, header, query, or default) and stores the
|
|
374
374
|
* result in the router context.
|
|
375
375
|
*
|
|
376
376
|
* Does not import or read from app config context; the consumer supplies config.
|
|
377
377
|
*/
|
|
378
|
-
function
|
|
378
|
+
function createSiteContextMiddleware(config) {
|
|
379
379
|
const siteDetectionConfig = {
|
|
380
380
|
...DEFAULT_SITE_DETECTION,
|
|
381
381
|
...config.siteDetectionConfig
|
|
@@ -384,8 +384,8 @@ function createMultiSiteMiddleware(config) {
|
|
|
384
384
|
...DEFAULT_LOCALE_DETECTION,
|
|
385
385
|
...config.localeDetectionConfig
|
|
386
386
|
};
|
|
387
|
-
const siteCookie =
|
|
388
|
-
const localeCookie =
|
|
387
|
+
const siteCookie = createSiteContextCookie(siteDetectionConfig.lookupCookie);
|
|
388
|
+
const localeCookie = createSiteContextCookie(localeDetectionConfig.lookupCookie);
|
|
389
389
|
const settings = {
|
|
390
390
|
...config,
|
|
391
391
|
siteDetectionConfig,
|
|
@@ -393,10 +393,10 @@ function createMultiSiteMiddleware(config) {
|
|
|
393
393
|
siteCookie,
|
|
394
394
|
localeCookie
|
|
395
395
|
};
|
|
396
|
-
const
|
|
396
|
+
const siteContextMiddleware = async ({ request, context }, next) => {
|
|
397
397
|
const site = await resolveSite(request, settings);
|
|
398
398
|
const locale = await resolveLocale(request, settings, site);
|
|
399
|
-
context.set(
|
|
399
|
+
context.set(siteContext, {
|
|
400
400
|
site,
|
|
401
401
|
locale,
|
|
402
402
|
siteCookie: settings.siteCookie,
|
|
@@ -411,9 +411,9 @@ function createMultiSiteMiddleware(config) {
|
|
|
411
411
|
if (localeSetCookie) response.headers.append("Set-Cookie", localeSetCookie);
|
|
412
412
|
return response;
|
|
413
413
|
};
|
|
414
|
-
return
|
|
414
|
+
return siteContextMiddleware;
|
|
415
415
|
}
|
|
416
416
|
|
|
417
417
|
//#endregion
|
|
418
|
-
export { SiteProvider, applyUrlConfig, buildUrl,
|
|
419
|
-
//# sourceMappingURL=
|
|
418
|
+
export { SiteProvider, applyUrlConfig, buildUrl, createSiteContextMiddleware, getSiteContextCookies, requestToLocaleMap, resolvePrefix, sanitizePrefix, siteContext, stripPathPrefix, useSite };
|
|
419
|
+
//# sourceMappingURL=site-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"site-context.js","names":["result: Record<string, string>","resolvers: Record<DetectionMethod, () => Promise<string | null>>","DEFAULT_SITE_DETECTION: Required<DetectionConfig>","DEFAULT_LOCALE_DETECTION: Required<DetectionConfig>","locale: Locale | null","resolvers: Record<DetectionMethod, () => Promise<string | null>>","createContext","siteDetectionConfig: SiteSettings['siteDetectionConfig']","localeDetectionConfig: SiteSettings['localeDetectionConfig']","settings: SiteSettings","siteContextMiddleware: MiddlewareFunction<Response>"],"sources":["../src/site-context/site-context.tsx","../src/site-context/build-url.ts","../src/site-context/utils.ts","../src/site-context/site-detection.ts","../src/site-context/configs.ts","../src/site-context/cookies.ts","../src/site-context/locale-detection.ts","../src/site-context/middleware.ts"],"sourcesContent":["/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { createContext, useContext, type PropsWithChildren } from 'react';\nimport type { Site } from './types';\n\nconst SiteContext = createContext<Site | undefined>(undefined);\n\n/**\n * Provides the current site to the component tree.\n * Follows the same pattern as CurrencyProvider.\n *\n * Mounted in the template (e.g., app-wrapper.tsx or root.tsx) with the resolved\n * site value from the loader/middleware.\n */\nexport function SiteProvider({ value, children }: PropsWithChildren<{ value: Site }>) {\n return <SiteContext.Provider value={value}>{children}</SiteContext.Provider>;\n}\n\n/**\n * React hook to get the current site.\n * Returns undefined when no SiteProvider is mounted.\n */\n// eslint-disable-next-line react-refresh/only-export-components\nexport function useSite(): Site | undefined {\n return useContext(SiteContext);\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { Url } from '../config/types';\n\n/**\n * Parses search config string into key-value pairs, preserving ':param' placeholders.\n * '?lng=:localeId&site=:siteId' → { lng: ':localeId', site: ':siteId' }\n */\nexport function parseSearchConfig(search: string): Record<string, string> {\n const searchParams = new URLSearchParams(search);\n const result: Record<string, string> = {};\n for (const [key, value] of searchParams) {\n result[key] = value;\n }\n return result;\n}\n\n/**\n * Extracts parameter names from a prefix string.\n * '/:siteId/:localeId' → ['siteId', 'localeId']\n */\nexport function extractPrefixParams(prefix: string): string[] {\n const matches = prefix.match(/:(\\w+)/g);\n return matches ? matches.map((m) => m.slice(1)) : [];\n}\n\n/**\n * Splits a URL string into its component parts.\n * '/product/123?color=red#details' → { pathname: '/product/123', search: 'color=red', hash: '#details' }\n */\nexport function decomposeUrl(url: string): { pathname: string; search: string; hash: string } {\n const hashIdx = url.indexOf('#');\n const hash = hashIdx >= 0 ? url.slice(hashIdx) : '';\n const withoutHash = hashIdx >= 0 ? url.slice(0, hashIdx) : url;\n const searchIdx = withoutHash.indexOf('?');\n const search = searchIdx >= 0 ? withoutHash.slice(searchIdx + 1) : '';\n const pathname = searchIdx >= 0 ? withoutHash.slice(0, searchIdx) : withoutHash;\n return { pathname, search, hash };\n}\n\n/**\n * Resolves a prefix template by replacing parameter placeholders with values.\n * ('/:siteId/:localeId', { siteId: 'global', localeId: 'en-GB' }) → '/global/en-GB'\n */\nexport function resolvePrefix(prefix: string, params: Record<string, string>): string {\n let resolved = prefix;\n for (const paramName of extractPrefixParams(prefix)) {\n const value = params[paramName];\n if (value) {\n resolved = resolved.replace(`:${paramName}`, value);\n }\n }\n return resolved;\n}\n\n/**\n * Strips the URL prefix segments from a pathname based on a prefix pattern.\n * Since all routes are configured with the prefix baked in, segment counting is sufficient.\n *\n * @param pathname - Full pathname (e.g. '/global/en-GB/checkout')\n * @param prefixPattern - URL prefix pattern from config (e.g. '/:siteId/:localeId')\n * @returns Pathname with prefix stripped (e.g. '/checkout'), or original if\n * the pathname has fewer segments than the prefix\n *\n * @example\n * stripPathPrefix('/global/en-GB/checkout', '/:siteId/:localeId') // → '/checkout'\n * stripPathPrefix('/checkout', '/:siteId/:localeId') // → '/checkout' (fewer segments → unchanged)\n * stripPathPrefix('/checkout', '') // → '/checkout' (no prefix configured)\n * stripPathPrefix('/', '/:siteId/:localeId') // → '/'\n */\nexport function stripPathPrefix(pathname: string, prefixPattern: string): string {\n if (!prefixPattern) return pathname;\n\n const prefixSegmentCount = prefixPattern.split('/').filter(Boolean).length;\n const pathSegments = pathname.split('/').filter(Boolean);\n\n if (pathSegments.length <= prefixSegmentCount) {\n return pathSegments.length === prefixSegmentCount ? '/' : pathname;\n }\n\n return `/${pathSegments.slice(prefixSegmentCount).join('/')}`;\n}\n\n/**\n * Sanitize a resolved prefix from a pathname if present.\n * sanitizePrefix('/global/en-GB/product/123', '/global/en-GB') → '/product/123'\n * sanitizePrefix('/product/123', '/global/en-GB') → '/product/123' (no-op)\n */\nexport function sanitizePrefix(pathname: string, pathPrefix: string): string {\n if (!pathPrefix) return pathname;\n if (pathname === pathPrefix) return '';\n if (pathname.startsWith(`${pathPrefix}/`)) return pathname.slice(pathPrefix.length);\n return pathname;\n}\n\n/**\n * Builds a fully-qualified URL with site context prefix and search params.\n *\n * Only keys defined in urlConfig.search are set by site context. Any other query params\n * already present on the `to` URL (including duplicate keys) are preserved as-is.\n * e.g. to='/api/search?refine=color:blue&refine=size:M', search='?lng=:localeId'\n * → '/api/search?refine=color:blue&refine=size:M&lng=en-GB'\n *\n * @example\n * buildUrl({ to: '/product/123', urlConfig: { prefix: '/:siteId', search: '?lng=:localeId' }, params: { siteId: 'global', localeId: 'en-GB' } })\n * // → '/global/product/123?lng=en-GB'\n */\nexport function buildUrl({\n to,\n urlConfig,\n params,\n}: {\n to: string;\n urlConfig?: Url;\n params: Record<string, string>;\n}): string {\n if (!urlConfig) return to;\n if (!to || to === '#' || to.startsWith('http') || to.startsWith('//')) return to;\n\n const { pathname, search: existingSearch, hash } = decomposeUrl(to);\n\n const pathPrefix = urlConfig.prefix && urlConfig.prefix !== '/' ? resolvePrefix(urlConfig.prefix, params) : '';\n // sanitize prefix to make sure there is no prefix duplication at any case\n const path = pathPrefix ? `${pathPrefix}${sanitizePrefix(pathname, pathPrefix)}` : pathname;\n\n const searchParams = new URLSearchParams(existingSearch);\n if (urlConfig.search) {\n const searchConfig = parseSearchConfig(urlConfig.search);\n for (const [queryKey, value] of Object.entries(searchConfig)) {\n if (value.startsWith(':')) {\n const paramValue = params[value.slice(1)];\n if (paramValue) {\n searchParams.set(queryKey, paramValue);\n }\n } else {\n searchParams.set(queryKey, value);\n }\n }\n }\n\n const search = searchParams.toString();\n return `${path}${search ? `?${search}` : ''}${hash}`;\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { Cookie } from 'react-router';\n\n/**\n * Extract a string value from the URL path segment at the given index.\n */\nexport function lookupFromPath(pathname: string, pathIndex: number): string | null {\n const pathSegments = pathname.split('/').filter(Boolean);\n\n if (pathSegments.length <= pathIndex) return null;\n\n return pathSegments[pathIndex];\n}\n\n/**\n * Detect a string value from cookie using the given cookie parser.\n *\n * Returns a promise that resolves to the cookie value.\n */\nexport async function readCookieFromRequest(request: Request, cookie: Cookie): Promise<string | null> {\n const cookies = request.headers.get('Cookie');\n if (!cookies) return null;\n\n const cookieValue = await cookie.parse(cookies);\n return cookieValue;\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { Cookie } from 'react-router';\nimport type { Site, SiteSettings, DetectionMethod } from './types';\nimport { readCookieFromRequest, lookupFromPath } from './utils';\n\n/**\n * Detect site reference from cookie.\n */\nexport async function readSiteFromCookie(request: Request, cookie: Cookie): Promise<string | null> {\n return readCookieFromRequest(request, cookie);\n}\n\n/**\n * Get site object using the site id or alias\n * 1. Check siteIdentifier against each site's alias; if matched, return that site.\n * 2. Else check against each site's id; if matched, return that site.\n * 3. If no match, return null.\n */\nfunction getSiteFromIdOrAlias(siteIdentifier: string | null, sites: Site[]): Site | null {\n if (!siteIdentifier) return null;\n return sites.find((site) => site.alias === siteIdentifier || site.id === siteIdentifier) ?? null;\n}\n\n/**\n * Resolve site using the configured detection order.\n * Returns the first valid site from the first source that yields a valid value.\n */\nexport async function resolveSite(request: Request, settings: SiteSettings): Promise<Site> {\n const { sites, defaultSiteId, siteDetectionConfig, siteCookie } = settings;\n\n const requestUrl = new URL(request.url);\n\n // When a base path is configured (e.g., '/shop'), we need to skip its path segments.\n // React Router handles the base path internally for hooks like useParams or useLocation,\n // but it does not strip it from request.url. The offset is calculated dynamically from\n // the number of segments in the base path as future-proof in case we support multi-segment\n // base paths in the future.\n const basePathOffset = process.env.MRT_ENV_BASE_PATH\n ? process.env.MRT_ENV_BASE_PATH.split('/').filter(Boolean).length\n : 0;\n\n const resolvers: Record<DetectionMethod, () => Promise<string | null>> = {\n path: () =>\n Promise.resolve(\n lookupFromPath(requestUrl.pathname, siteDetectionConfig.lookupFromPathIndex + basePathOffset)\n ),\n querystring: () => Promise.resolve(requestUrl.searchParams.get(siteDetectionConfig.lookupQuerystring)),\n header: () => Promise.resolve(request.headers.get(siteDetectionConfig.lookupHeader)),\n cookie: async () => readSiteFromCookie(request, siteCookie),\n };\n\n for (const method of siteDetectionConfig.order) {\n const siteIdOrAlias = await resolvers[method]?.();\n const resolvedSite = getSiteFromIdOrAlias(siteIdOrAlias, sites);\n if (resolvedSite) return resolvedSite;\n }\n\n // If no site id was found, use the default site id\n const site = getSiteFromIdOrAlias(defaultSiteId, sites);\n\n // If default site id is invalid, throw an error\n if (!site) {\n throw new Error(`Default site ${defaultSiteId} not found.`);\n }\n\n return site;\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { DetectionConfig } from './types';\n\n/**\n * Default site detection configuration\n */\nexport const DEFAULT_SITE_DETECTION: Required<DetectionConfig> = {\n order: ['path', 'querystring', 'cookie', 'header'],\n lookupFromPathIndex: 0,\n lookupQuerystring: 'site',\n lookupCookie: 'site_id',\n lookupHeader: 'X-Site-Id',\n caches: ['cookie'],\n};\n\n/**\n * Default locale detection configuration\n */\nexport const DEFAULT_LOCALE_DETECTION: Required<DetectionConfig> = {\n order: ['path', 'querystring', 'cookie', 'header'],\n lookupFromPathIndex: 1,\n lookupQuerystring: 'lng',\n lookupCookie: 'lng',\n lookupHeader: 'Accept-Language',\n caches: ['cookie'],\n};\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { createCookie, type Cookie } from 'react-router';\n\n/**\n * Cookie options for site context cookies\n */\nexport const COOKIE_OPTIONS = {\n path: '/',\n sameSite: 'lax' as const,\n secure: process.env.NODE_ENV === 'production',\n httpOnly: true,\n};\n\n/**\n * Creates a cookie instance with the given name.\n *\n * @param name - Cookie name\n * @returns Cookie instance configured with site context options\n */\nexport function createSiteContextCookie(name: string): Cookie {\n return createCookie(name, COOKIE_OPTIONS);\n}\n\n/**\n * WeakMap to pass resolved locale from site context middleware to i18next's findLocale.\n * WeakMap allows garbage collection when requests are done.\n * This is necessary because findLocale() only receives the Request object, not the router context.\n */\nexport const requestToLocaleMap = new WeakMap<Request, string>();\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { Cookie } from 'react-router';\nimport type { DetectionMethod, Locale, SiteSettings, Site } from './types';\nimport { readCookieFromRequest, lookupFromPath } from './utils';\n\n/**\n * Read locale from cookie.\n */\nexport async function readLocaleFromCookie(request: Request, cookie: Cookie): Promise<string | null> {\n return readCookieFromRequest(request, cookie);\n}\n\n/**\n * Get locale object using the locale id or alias.\n * 1. Check localeIdOrAlias against each locale's alias; if matched, return that locale.\n * 2. Else check against each locale's id; if matched, return that locale.\n * 3. If no match, return null (caller should use defaultLocale).\n *\n * @param localeIdentifier - The locale id or alias to get the locale from. Null is allowed because this may come from\n * extrenal sources such as cookies, headers, or query parameters.\n * @param locales - The list of locales to search through.\n * @returns The locale object if found, otherwise null.\n */\nfunction getLocaleFromIdOrAlias(localeIdentifier: string | undefined | null, locales: Locale[]): Locale | null {\n if (!localeIdentifier) return null;\n return locales.find((locale) => locale.alias === localeIdentifier || locale.id === localeIdentifier) ?? null;\n}\n\n/**\n * Resolve locale using the configured detection order.\n * Returns the first valid locale from the first source that yields a valid value.\n */\nexport async function resolveLocale(request: Request, settings: SiteSettings, site: Site): Promise<Locale> {\n const { defaultLocale, localeDetectionConfig, localeCookie } = settings;\n const { supportedLocales } = site;\n\n let locale: Locale | null = null;\n const requestUrl = new URL(request.url);\n\n // When a base path is configured (e.g., '/shop'), we need to skip its path segments.\n // React Router handles the base path internally for hooks like useParams or useLocation,\n // but it does not strip it from request.url. The offset is calculated dynamically from\n // the number of segments in the base path as future-proof in case we support multi-segment\n // base paths in the future.\n const basePathOffset = process.env.MRT_ENV_BASE_PATH\n ? process.env.MRT_ENV_BASE_PATH.split('/').filter(Boolean).length\n : 0;\n\n const resolvers: Record<DetectionMethod, () => Promise<string | null>> = {\n path: () =>\n Promise.resolve(\n lookupFromPath(requestUrl.pathname, localeDetectionConfig.lookupFromPathIndex + basePathOffset)\n ),\n querystring: () => Promise.resolve(requestUrl.searchParams.get(localeDetectionConfig.lookupQuerystring)),\n header: () => Promise.resolve(request.headers.get(localeDetectionConfig.lookupHeader)),\n cookie: async () => readLocaleFromCookie(request, localeCookie),\n };\n\n for (const method of localeDetectionConfig.order) {\n const localeIdOrAlias = await resolvers[method]?.();\n const resolvedLocale = getLocaleFromIdOrAlias(localeIdOrAlias, supportedLocales);\n if (resolvedLocale) return resolvedLocale;\n }\n\n // If no locale was found, use the default locale\n if (!locale) {\n locale = getLocaleFromIdOrAlias(defaultLocale, supportedLocales);\n }\n\n // If default locale is invalid, throw an error\n if (!locale) {\n throw new Error(\n `Default locale ${defaultLocale} not found in the list of supported locales for site ${site.id}.`\n );\n }\n\n return locale;\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { createContext, type MiddlewareFunction, type RouterContextProvider } from 'react-router';\nimport { resolveSite } from './site-detection';\nimport type { SiteConfig, SiteContext, SiteSettings, Site, Locale } from './types';\nimport { DEFAULT_SITE_DETECTION, DEFAULT_LOCALE_DETECTION } from './configs';\nimport { createSiteContextCookie, requestToLocaleMap } from './cookies';\nimport { resolveLocale } from './locale-detection';\n\nexport const siteContext = createContext<SiteContext | null>(null);\n\ntype MiddlewareArgs = { request: Request; context: Readonly<RouterContextProvider> };\n\n/**\n * Helper function to get site context cookies from router context.\n * Useful in server actions and loaders that need to read/set cookies.\n *\n * @param context - Router context provider\n * @returns Object with siteCookie and localeCookie instances, or null if context not set\n *\n * @example\n * ```typescript\n * export const action: ActionFunction = async ({ request, context }) => {\n * const cookies = getSiteContextCookies(context);\n * if (cookies) {\n * const cookieHeader = await cookies.localeCookie.serialize(locale);\n * // ... use cookieHeader\n * }\n * };\n * ```\n */\nexport function getSiteContextCookies(context: Readonly<RouterContextProvider>) {\n const siteCtx = context.get(siteContext);\n if (!siteCtx) return null;\n return {\n siteCookie: siteCtx.siteCookie,\n localeCookie: siteCtx.localeCookie,\n };\n}\n\n/**\n * Helper function to determine if cookies should be set based on:\n * 1. Whether caching is enabled for each cookie type\n * 2. Whether the resolved value differs from the existing cookie\n * 3. Whether cookies were already set by actions/loaders in the response\n *\n * @param request - Incoming request\n * @param response - Response from next()\n * @param settings - Site context settings with cookie instances and detection config\n * @param site - Resolved site for this request\n * @param locale - Resolved locale for this request\n * @returns Object with shouldSetSiteCookie and shouldSetLocaleCookie booleans\n */\nasync function shouldSetCookies(\n request: Request,\n response: Response,\n settings: SiteSettings,\n site: Site,\n locale: Locale\n): Promise<{ shouldSetSiteCookie: boolean; shouldSetLocaleCookie: boolean }> {\n const cacheSite = settings.siteDetectionConfig.caches?.includes('cookie');\n const cacheLocale = settings.localeDetectionConfig.caches?.includes('cookie');\n\n // Early return if no cookie caching is enabled\n if (!cacheSite && !cacheLocale) {\n return { shouldSetSiteCookie: false, shouldSetLocaleCookie: false };\n }\n\n // Check if cookies were already set by actions/loaders in the response.\n // If they were, we don't want to override them.\n const responseSetCookies = response.headers.getSetCookie?.() || [];\n const isSettingSiteCookieInResponse = responseSetCookies.some((cookie) =>\n cookie.startsWith(`${settings.siteCookie.name}=`)\n );\n const isSettingLocaleCookieInResponse = responseSetCookies.some((cookie) =>\n cookie.startsWith(`${settings.localeCookie.name}=`)\n );\n\n const requestCookieHeader = request.headers.get('Cookie');\n const [existingSiteCookie, existingLocaleCookie] = await Promise.all([\n settings.siteCookie.parse(requestCookieHeader),\n settings.localeCookie.parse(requestCookieHeader),\n ]);\n\n // Set cookie if: doesn't exist yet OR resolved value differs from existing.\n // Skip if an action/loader already set it in the response.\n return {\n shouldSetSiteCookie: cacheSite && !isSettingSiteCookieInResponse && existingSiteCookie !== site.id,\n shouldSetLocaleCookie: cacheLocale && !isSettingLocaleCookieInResponse && existingLocaleCookie !== locale.id,\n };\n}\n\n/**\n * Creates a site context middleware that resolves the current site from\n * the request (path, cookie, header, query, or default) and stores the\n * result in the router context.\n *\n * Does not import or read from app config context; the consumer supplies config.\n */\nexport function createSiteContextMiddleware(config: SiteConfig): MiddlewareFunction<Response> {\n // Merge config with defaults so every detection option has a value\n const siteDetectionConfig: SiteSettings['siteDetectionConfig'] = {\n ...DEFAULT_SITE_DETECTION,\n ...config.siteDetectionConfig,\n };\n const localeDetectionConfig: SiteSettings['localeDetectionConfig'] = {\n ...DEFAULT_LOCALE_DETECTION,\n ...config.localeDetectionConfig,\n };\n\n // Create cookies based on configured names\n const siteCookie = createSiteContextCookie(siteDetectionConfig.lookupCookie);\n const localeCookie = createSiteContextCookie(localeDetectionConfig.lookupCookie);\n\n const settings: SiteSettings = {\n ...config,\n siteDetectionConfig,\n localeDetectionConfig,\n siteCookie,\n localeCookie,\n };\n\n const siteContextMiddleware: MiddlewareFunction<Response> = async (\n { request, context }: MiddlewareArgs,\n next: () => Promise<Response>\n ): Promise<Response> => {\n const site = await resolveSite(request, settings);\n const locale = await resolveLocale(request, settings, site);\n\n // Store full Site, Locale, and Cookie objects in context for downstream middlewares (currency, loaders, etc.)\n context.set(siteContext, {\n site,\n locale,\n siteCookie: settings.siteCookie,\n localeCookie: settings.localeCookie,\n });\n\n // Store locale in a WeakMap so i18next's findLocale can access it\n // This is necessary because findLocale only receives Request and cannot access the router context\n requestToLocaleMap.set(request, locale.id);\n\n const response = await next();\n\n // Determine if cookies should be set\n const { shouldSetSiteCookie, shouldSetLocaleCookie } = await shouldSetCookies(\n request,\n response,\n settings,\n site,\n locale\n );\n\n // Early return if no cookies need to be set\n if (!shouldSetSiteCookie && !shouldSetLocaleCookie) {\n return response;\n }\n\n const [siteSetCookie, localeSetCookie] = await Promise.all([\n shouldSetSiteCookie ? settings.siteCookie.serialize(site.id, { path: '/' }) : Promise.resolve(null),\n shouldSetLocaleCookie ? settings.localeCookie.serialize(locale.id, { path: '/' }) : Promise.resolve(null),\n ]);\n\n if (siteSetCookie) response.headers.append('Set-Cookie', siteSetCookie);\n if (localeSetCookie) response.headers.append('Set-Cookie', localeSetCookie);\n\n return response;\n };\n\n return siteContextMiddleware;\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@salesforce/storefront-next-runtime",
|
|
3
|
-
"version": "0.3.0
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Runtime agnostic libraries for SFCC Storefront Next",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -62,10 +62,10 @@
|
|
|
62
62
|
"default": "./dist/events.js"
|
|
63
63
|
}
|
|
64
64
|
},
|
|
65
|
-
"./
|
|
65
|
+
"./site-context": {
|
|
66
66
|
"import": {
|
|
67
|
-
"types": "./dist/
|
|
68
|
-
"default": "./dist/
|
|
67
|
+
"types": "./dist/site-context.d.ts",
|
|
68
|
+
"default": "./dist/site-context.js"
|
|
69
69
|
}
|
|
70
70
|
},
|
|
71
71
|
"./scapi": {
|
package/dist/multi-site.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"multi-site.d.ts","names":[],"sources":["../src/multi-site/types.ts","../src/multi-site/site-context.tsx","../src/multi-site/apply-url-config.ts","../src/multi-site/build-url.ts","../src/multi-site/middleware.ts","../src/multi-site/cookies.ts"],"sourcesContent":[],"mappings":";;;;;;;;;AAiCgB,KAbJ,MAAA,GAAS,QAaL,GAAA;EACE,KAAA,CAAA,EAAA,MAAA;CAAM;AAOZ,KAjBA,IAAA,GAAO,IAiBQ,CAjBH,MAiBG,EAAA,kBAAA,CAAA,GAAA;EAChB,IAAA,CAAA,EAAA,MAAA;EAGe,KAAA,CAAA,EAAA,MAAA;EACE,gBAAA,EAnBN,MAmBM,EAAA;CAAe;AAc/B,KA9BA,gBAAA,GA8Be;EAGf,IAAA,EAhCF,IAgCE;UA/BA;cACI;gBACE;ACPlB,CAAA;;;;;AAAoF,KDcxE,eAAA,GCdwE;EAAA,KAAA,EDezE,ICfyE,EAAA;EASpE,aAAO,EAAA,MAAA;;wBDSG;0BACE;AE0E5B,CAAA;ACnBA;AAmBgB,KH5DJ,eAAA,GG4DY,MAAA,GAAA,aAAA,GAAA,QAAA,GAAA,QAAA;AACpB,KH1DQ,eAAA,GG0DR;EACA,KAAA,EH1DO,eG0DP,EAAA;EACA,mBAAA,CAAA,EAAA,MAAA;EAGY,iBAAA,CAAA,EAAA,MAAA;EACJ,YAAA,CAAA,EAAA,MAAA;EAAM,YAAA,CAAA,EAAA,MAAA;WH1DL;;;;;;;AA5Bb;;;;AAK2C,iBCnB3B,YAAA,CDmB2B;EAAA,KAAA;EAAA;AAiB3C,CAjB2C,ECnBO,iBDmBP,CAAA;EAc/B,KAAA,ECjCiE,IDiCjE;AAGZ,CAAA,CAAA,CAAA,ECpCoF,kBAAA,CAAA,GAAA,CAAA,ODqCzE;;;;ACrCX;AAA+B,iBASf,OAAA,CAAA,CATe,EASJ,IATI,GAAA,SAAA;;;;AC6F/B;;;;;;;;AC/DA;AA0BA;AAkBA;AAmBA;;AAEI,iBDFY,cAAA,CCEZ,OAAA,EAAA;EACA,MAAA,EDFQ,gBCER,EAAA;EAGY,SAAA,CAAA,EDJA,GCIA;EACJ,WAAA,EAAA,MAAA;CAAM,CAAA,EDHd,gBCGc,EAAA;;;;;AF3FlB;;iBEqBgB,aAAA,yBAAsC;;AD+DtD;;;;;;;;AC/DA;AA0BA;AAkBA;AAmBA;;;AAGI,iBAxCY,eAAA,CAwCZ,QAAA,EAAA,MAAA,EAAA,aAAA,EAAA,MAAA,CAAA,EAAA,MAAA;;;;;;iBAtBY,cAAA;AC9EhB;AAsBA;;;;;;AAoEA;;;;;iBDOgB,QAAA;;;;;;cAMA;EEnFH,MAAA,EFoFD,MEpFC,CAAA,MAAmD,EAAA,MAAA,CAAA;;;;ALVhD,cIVH,gBJUG,EIVa,aAAA,CAAA,aJUb,CIVa,gBJUb,GAAA,IAAA,CAAA;;;AAQhB;;;;;AAmBA;AAGA;;;;ACpCA;;;;;;AAAoF,iBGkBpE,mBAAA,CHlBoE,OAAA,EGkBvC,QHlBuC,CGkB9B,qBHlB8B,CAAA,CAAA,EAAA;EASpE,UAAO,EGS8B,aAAA,CAAA,MHTtB;;;;ACoF/B;;;;;;iBEPgB,yBAAA,SAAkC,kBAAkB,mBAAmB;;;;;;;;AH7EvE,cIOH,kBJPkB,EIOA,OJPA,CIOA,OJPA,EAAA,MAAA,CAAA"}
|
package/dist/multi-site.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"multi-site.js","names":["result: Record<string, string>","resolvers: Record<DetectionMethod, () => Promise<string | null>>","DEFAULT_SITE_DETECTION: Required<DetectionConfig>","DEFAULT_LOCALE_DETECTION: Required<DetectionConfig>","locale: Locale | null","resolvers: Record<DetectionMethod, () => Promise<string | null>>","createContext","siteDetectionConfig: MultiSiteSettings['siteDetectionConfig']","localeDetectionConfig: MultiSiteSettings['localeDetectionConfig']","settings: MultiSiteSettings","multiSiteMiddleware: MiddlewareFunction<Response>"],"sources":["../src/multi-site/site-context.tsx","../src/multi-site/build-url.ts","../src/multi-site/utils.ts","../src/multi-site/site-detection.ts","../src/multi-site/configs.ts","../src/multi-site/cookies.ts","../src/multi-site/locale-detection.ts","../src/multi-site/middleware.ts"],"sourcesContent":["/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { createContext, useContext, type PropsWithChildren } from 'react';\nimport type { Site } from './types';\n\nconst SiteContext = createContext<Site | undefined>(undefined);\n\n/**\n * Provides the current site to the component tree.\n * Follows the same pattern as CurrencyProvider.\n *\n * Mounted in the template (e.g., app-wrapper.tsx or root.tsx) with the resolved\n * site value from the loader/middleware.\n */\nexport function SiteProvider({ value, children }: PropsWithChildren<{ value: Site }>) {\n return <SiteContext.Provider value={value}>{children}</SiteContext.Provider>;\n}\n\n/**\n * React hook to get the current site.\n * Returns undefined when no SiteProvider is mounted.\n */\n// eslint-disable-next-line react-refresh/only-export-components\nexport function useSite(): Site | undefined {\n return useContext(SiteContext);\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { Url } from '../config/types';\n\n/**\n * Parses search config string into key-value pairs, preserving ':param' placeholders.\n * '?lng=:localeId&site=:siteId' → { lng: ':localeId', site: ':siteId' }\n */\nexport function parseSearchConfig(search: string): Record<string, string> {\n const searchParams = new URLSearchParams(search);\n const result: Record<string, string> = {};\n for (const [key, value] of searchParams) {\n result[key] = value;\n }\n return result;\n}\n\n/**\n * Extracts parameter names from a prefix string.\n * '/:siteId/:localeId' → ['siteId', 'localeId']\n */\nexport function extractPrefixParams(prefix: string): string[] {\n const matches = prefix.match(/:(\\w+)/g);\n return matches ? matches.map((m) => m.slice(1)) : [];\n}\n\n/**\n * Splits a URL string into its component parts.\n * '/product/123?color=red#details' → { pathname: '/product/123', search: 'color=red', hash: '#details' }\n */\nexport function decomposeUrl(url: string): { pathname: string; search: string; hash: string } {\n const hashIdx = url.indexOf('#');\n const hash = hashIdx >= 0 ? url.slice(hashIdx) : '';\n const withoutHash = hashIdx >= 0 ? url.slice(0, hashIdx) : url;\n const searchIdx = withoutHash.indexOf('?');\n const search = searchIdx >= 0 ? withoutHash.slice(searchIdx + 1) : '';\n const pathname = searchIdx >= 0 ? withoutHash.slice(0, searchIdx) : withoutHash;\n return { pathname, search, hash };\n}\n\n/**\n * Resolves a prefix template by replacing parameter placeholders with values.\n * ('/:siteId/:localeId', { siteId: 'global', localeId: 'en-GB' }) → '/global/en-GB'\n */\nexport function resolvePrefix(prefix: string, params: Record<string, string>): string {\n let resolved = prefix;\n for (const paramName of extractPrefixParams(prefix)) {\n const value = params[paramName];\n if (value) {\n resolved = resolved.replace(`:${paramName}`, value);\n }\n }\n return resolved;\n}\n\n/**\n * Strips the URL prefix segments from a pathname based on a prefix pattern.\n * Since all routes are configured with the prefix baked in, segment counting is sufficient.\n *\n * @param pathname - Full pathname (e.g. '/global/en-GB/checkout')\n * @param prefixPattern - URL prefix pattern from config (e.g. '/:siteId/:localeId')\n * @returns Pathname with prefix stripped (e.g. '/checkout'), or original if\n * the pathname has fewer segments than the prefix\n *\n * @example\n * stripPathPrefix('/global/en-GB/checkout', '/:siteId/:localeId') // → '/checkout'\n * stripPathPrefix('/checkout', '/:siteId/:localeId') // → '/checkout' (fewer segments → unchanged)\n * stripPathPrefix('/checkout', '') // → '/checkout' (no prefix configured)\n * stripPathPrefix('/', '/:siteId/:localeId') // → '/'\n */\nexport function stripPathPrefix(pathname: string, prefixPattern: string): string {\n if (!prefixPattern) return pathname;\n\n const prefixSegmentCount = prefixPattern.split('/').filter(Boolean).length;\n const pathSegments = pathname.split('/').filter(Boolean);\n\n if (pathSegments.length <= prefixSegmentCount) {\n return pathSegments.length === prefixSegmentCount ? '/' : pathname;\n }\n\n return `/${pathSegments.slice(prefixSegmentCount).join('/')}`;\n}\n\n/**\n * Sanitize a resolved prefix from a pathname if present.\n * sanitizePrefix('/global/en-GB/product/123', '/global/en-GB') → '/product/123'\n * sanitizePrefix('/product/123', '/global/en-GB') → '/product/123' (no-op)\n */\nexport function sanitizePrefix(pathname: string, pathPrefix: string): string {\n if (!pathPrefix) return pathname;\n if (pathname === pathPrefix) return '';\n if (pathname.startsWith(`${pathPrefix}/`)) return pathname.slice(pathPrefix.length);\n return pathname;\n}\n\n/**\n * Builds a fully-qualified URL with multi-site prefix and search params.\n *\n * Only keys defined in urlConfig.search are set by multi-site. Any other query params\n * already present on the `to` URL (including duplicate keys) are preserved as-is.\n * e.g. to='/api/search?refine=color:blue&refine=size:M', search='?lng=:localeId'\n * → '/api/search?refine=color:blue&refine=size:M&lng=en-GB'\n *\n * @example\n * buildUrl({ to: '/product/123', urlConfig: { prefix: '/:siteId', search: '?lng=:localeId' }, params: { siteId: 'global', localeId: 'en-GB' } })\n * // → '/global/product/123?lng=en-GB'\n */\nexport function buildUrl({\n to,\n urlConfig,\n params,\n}: {\n to: string;\n urlConfig?: Url;\n params: Record<string, string>;\n}): string {\n if (!urlConfig) return to;\n if (!to || to === '#' || to.startsWith('http') || to.startsWith('//')) return to;\n\n const { pathname, search: existingSearch, hash } = decomposeUrl(to);\n\n const pathPrefix = urlConfig.prefix && urlConfig.prefix !== '/' ? resolvePrefix(urlConfig.prefix, params) : '';\n // sanitize prefix to make sure there is no prefix duplication at any case\n const path = pathPrefix ? `${pathPrefix}${sanitizePrefix(pathname, pathPrefix)}` : pathname;\n\n const searchParams = new URLSearchParams(existingSearch);\n if (urlConfig.search) {\n const searchConfig = parseSearchConfig(urlConfig.search);\n for (const [queryKey, value] of Object.entries(searchConfig)) {\n if (value.startsWith(':')) {\n const paramValue = params[value.slice(1)];\n if (paramValue) {\n searchParams.set(queryKey, paramValue);\n }\n } else {\n searchParams.set(queryKey, value);\n }\n }\n }\n\n const search = searchParams.toString();\n return `${path}${search ? `?${search}` : ''}${hash}`;\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { Cookie } from 'react-router';\n\n/**\n * Extract a string value from the URL path segment at the given index.\n */\nexport function lookupFromPath(pathname: string, pathIndex: number): string | null {\n const pathSegments = pathname.split('/').filter(Boolean);\n\n if (pathSegments.length <= pathIndex) return null;\n\n return pathSegments[pathIndex];\n}\n\n/**\n * Detect a string value from cookie using the given cookie parser.\n *\n * Returns a promise that resolves to the cookie value.\n */\nexport async function readCookieFromRequest(request: Request, cookie: Cookie): Promise<string | null> {\n const cookies = request.headers.get('Cookie');\n if (!cookies) return null;\n\n const cookieValue = await cookie.parse(cookies);\n return cookieValue;\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { Cookie } from 'react-router';\nimport type { Site, MultiSiteSettings, DetectionMethod } from './types';\nimport { readCookieFromRequest, lookupFromPath } from './utils';\n\n/**\n * Detect site reference from cookie.\n */\nexport async function readSiteFromCookie(request: Request, cookie: Cookie): Promise<string | null> {\n return readCookieFromRequest(request, cookie);\n}\n\n/**\n * Get site object using the site id or alias\n * 1. Check siteIdentifier against each site's alias; if matched, return that site.\n * 2. Else check against each site's id; if matched, return that site.\n * 3. If no match, return null.\n */\nfunction getSiteFromIdOrAlias(siteIdentifier: string | null, sites: Site[]): Site | null {\n if (!siteIdentifier) return null;\n return sites.find((site) => site.alias === siteIdentifier || site.id === siteIdentifier) ?? null;\n}\n\n/**\n * Resolve site using the configured detection order.\n * Returns the first valid site from the first source that yields a valid value.\n */\nexport async function resolveSite(request: Request, settings: MultiSiteSettings): Promise<Site> {\n const { sites, defaultSiteId, siteDetectionConfig, siteCookie } = settings;\n\n const requestUrl = new URL(request.url);\n\n // When a base path is configured (e.g., '/shop'), we need to skip its path segments.\n // React Router handles the base path internally for hooks like useParams or useLocation,\n // but it does not strip it from request.url. The offset is calculated dynamically from\n // the number of segments in the base path as future-proof in case we support multi-segment\n // base paths in the future.\n const basePathOffset = process.env.MRT_ENV_BASE_PATH\n ? process.env.MRT_ENV_BASE_PATH.split('/').filter(Boolean).length\n : 0;\n\n const resolvers: Record<DetectionMethod, () => Promise<string | null>> = {\n path: () =>\n Promise.resolve(\n lookupFromPath(requestUrl.pathname, siteDetectionConfig.lookupFromPathIndex + basePathOffset)\n ),\n querystring: () => Promise.resolve(requestUrl.searchParams.get(siteDetectionConfig.lookupQuerystring)),\n header: () => Promise.resolve(request.headers.get(siteDetectionConfig.lookupHeader)),\n cookie: async () => readSiteFromCookie(request, siteCookie),\n };\n\n for (const method of siteDetectionConfig.order) {\n const siteIdOrAlias = await resolvers[method]?.();\n const resolvedSite = getSiteFromIdOrAlias(siteIdOrAlias, sites);\n if (resolvedSite) return resolvedSite;\n }\n\n // If no site id was found, use the default site id\n const site = getSiteFromIdOrAlias(defaultSiteId, sites);\n\n // If default site id is invalid, throw an error\n if (!site) {\n throw new Error(`Default site ${defaultSiteId} not found.`);\n }\n\n return site;\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { DetectionConfig } from './types';\n\n/**\n * Default site detection configuration\n */\nexport const DEFAULT_SITE_DETECTION: Required<DetectionConfig> = {\n order: ['path', 'querystring', 'cookie', 'header'],\n lookupFromPathIndex: 0,\n lookupQuerystring: 'site',\n lookupCookie: 'site_id',\n lookupHeader: 'X-Site-Id',\n caches: ['cookie'],\n};\n\n/**\n * Default locale detection configuration\n */\nexport const DEFAULT_LOCALE_DETECTION: Required<DetectionConfig> = {\n order: ['path', 'querystring', 'cookie', 'header'],\n lookupFromPathIndex: 1,\n lookupQuerystring: 'lng',\n lookupCookie: 'lng',\n lookupHeader: 'Accept-Language',\n caches: ['cookie'],\n};\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { createCookie, type Cookie } from 'react-router';\n\n/**\n * Cookie options for multi-site cookies\n */\nexport const COOKIE_OPTIONS = {\n path: '/',\n sameSite: 'lax' as const,\n secure: process.env.NODE_ENV === 'production',\n httpOnly: true,\n};\n\n/**\n * Creates a cookie instance with the given name.\n *\n * @param name - Cookie name\n * @returns Cookie instance configured with multi-site options\n */\nexport function createMultiSiteCookie(name: string): Cookie {\n return createCookie(name, COOKIE_OPTIONS);\n}\n\n/**\n * WeakMap to pass resolved locale from multi-site middleware to i18next's findLocale.\n * WeakMap allows garbage collection when requests are done.\n * This is necessary because findLocale() only receives the Request object, not the router context.\n */\nexport const requestToLocaleMap = new WeakMap<Request, string>();\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { Cookie } from 'react-router';\nimport type { DetectionMethod, Locale, MultiSiteSettings, Site } from './types';\nimport { readCookieFromRequest, lookupFromPath } from './utils';\n\n/**\n * Read locale from cookie.\n */\nexport async function readLocaleFromCookie(request: Request, cookie: Cookie): Promise<string | null> {\n return readCookieFromRequest(request, cookie);\n}\n\n/**\n * Get locale object using the locale id or alias.\n * 1. Check localeIdOrAlias against each locale's alias; if matched, return that locale.\n * 2. Else check against each locale's id; if matched, return that locale.\n * 3. If no match, return null (caller should use defaultLocale).\n *\n * @param localeIdentifier - The locale id or alias to get the locale from. Null is allowed because this may come from\n * extrenal sources such as cookies, headers, or query parameters.\n * @param locales - The list of locales to search through.\n * @returns The locale object if found, otherwise null.\n */\nfunction getLocaleFromIdOrAlias(localeIdentifier: string | undefined | null, locales: Locale[]): Locale | null {\n if (!localeIdentifier) return null;\n return locales.find((locale) => locale.alias === localeIdentifier || locale.id === localeIdentifier) ?? null;\n}\n\n/**\n * Resolve locale using the configured detection order.\n * Returns the first valid locale from the first source that yields a valid value.\n */\nexport async function resolveLocale(request: Request, settings: MultiSiteSettings, site: Site): Promise<Locale> {\n const { defaultLocale, localeDetectionConfig, localeCookie } = settings;\n const { supportedLocales } = site;\n\n let locale: Locale | null = null;\n const requestUrl = new URL(request.url);\n\n // When a base path is configured (e.g., '/shop'), we need to skip its path segments.\n // React Router handles the base path internally for hooks like useParams or useLocation,\n // but it does not strip it from request.url. The offset is calculated dynamically from\n // the number of segments in the base path as future-proof in case we support multi-segment\n // base paths in the future.\n const basePathOffset = process.env.MRT_ENV_BASE_PATH\n ? process.env.MRT_ENV_BASE_PATH.split('/').filter(Boolean).length\n : 0;\n\n const resolvers: Record<DetectionMethod, () => Promise<string | null>> = {\n path: () =>\n Promise.resolve(\n lookupFromPath(requestUrl.pathname, localeDetectionConfig.lookupFromPathIndex + basePathOffset)\n ),\n querystring: () => Promise.resolve(requestUrl.searchParams.get(localeDetectionConfig.lookupQuerystring)),\n header: () => Promise.resolve(request.headers.get(localeDetectionConfig.lookupHeader)),\n cookie: async () => readLocaleFromCookie(request, localeCookie),\n };\n\n for (const method of localeDetectionConfig.order) {\n const localeIdOrAlias = await resolvers[method]?.();\n const resolvedLocale = getLocaleFromIdOrAlias(localeIdOrAlias, supportedLocales);\n if (resolvedLocale) return resolvedLocale;\n }\n\n // If no locale was found, use the default locale\n if (!locale) {\n locale = getLocaleFromIdOrAlias(defaultLocale, supportedLocales);\n }\n\n // If default locale is invalid, throw an error\n if (!locale) {\n throw new Error(\n `Default locale ${defaultLocale} not found in the list of supported locales for site ${site.id}.`\n );\n }\n\n return locale;\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { createContext, type MiddlewareFunction, type RouterContextProvider } from 'react-router';\nimport { resolveSite } from './site-detection';\nimport type { MultiSiteConfig, MultiSiteContext, MultiSiteSettings, Site, Locale } from './types';\nimport { DEFAULT_SITE_DETECTION, DEFAULT_LOCALE_DETECTION } from './configs';\nimport { createMultiSiteCookie, requestToLocaleMap } from './cookies';\nimport { resolveLocale } from './locale-detection';\n\nexport const multiSiteContext = createContext<MultiSiteContext | null>(null);\n\ntype MiddlewareArgs = { request: Request; context: Readonly<RouterContextProvider> };\n\n/**\n * Helper function to get multi-site cookies from router context.\n * Useful in server actions and loaders that need to read/set cookies.\n *\n * @param context - Router context provider\n * @returns Object with siteCookie and localeCookie instances, or null if context not set\n *\n * @example\n * ```typescript\n * export const action: ActionFunction = async ({ request, context }) => {\n * const cookies = getMultiSiteCookies(context);\n * if (cookies) {\n * const cookieHeader = await cookies.localeCookie.serialize(locale);\n * // ... use cookieHeader\n * }\n * };\n * ```\n */\nexport function getMultiSiteCookies(context: Readonly<RouterContextProvider>) {\n const multiSite = context.get(multiSiteContext);\n if (!multiSite) return null;\n return {\n siteCookie: multiSite.siteCookie,\n localeCookie: multiSite.localeCookie,\n };\n}\n\n/**\n * Helper function to determine if cookies should be set based on:\n * 1. Whether caching is enabled for each cookie type\n * 2. Whether the resolved value differs from the existing cookie\n * 3. Whether cookies were already set by actions/loaders in the response\n *\n * @param request - Incoming request\n * @param response - Response from next()\n * @param settings - Multi-site settings with cookie instances and detection config\n * @param site - Resolved site for this request\n * @param locale - Resolved locale for this request\n * @returns Object with shouldSetSiteCookie and shouldSetLocaleCookie booleans\n */\nasync function shouldSetCookies(\n request: Request,\n response: Response,\n settings: MultiSiteSettings,\n site: Site,\n locale: Locale\n): Promise<{ shouldSetSiteCookie: boolean; shouldSetLocaleCookie: boolean }> {\n const cacheSite = settings.siteDetectionConfig.caches?.includes('cookie');\n const cacheLocale = settings.localeDetectionConfig.caches?.includes('cookie');\n\n // Early return if no cookie caching is enabled\n if (!cacheSite && !cacheLocale) {\n return { shouldSetSiteCookie: false, shouldSetLocaleCookie: false };\n }\n\n // Check if cookies were already set by actions/loaders in the response.\n // If they were, we don't want to override them.\n const responseSetCookies = response.headers.getSetCookie?.() || [];\n const isSettingSiteCookieInResponse = responseSetCookies.some((cookie) =>\n cookie.startsWith(`${settings.siteCookie.name}=`)\n );\n const isSettingLocaleCookieInResponse = responseSetCookies.some((cookie) =>\n cookie.startsWith(`${settings.localeCookie.name}=`)\n );\n\n const requestCookieHeader = request.headers.get('Cookie');\n const [existingSiteCookie, existingLocaleCookie] = await Promise.all([\n settings.siteCookie.parse(requestCookieHeader),\n settings.localeCookie.parse(requestCookieHeader),\n ]);\n\n // Set cookie if: doesn't exist yet OR resolved value differs from existing.\n // Skip if an action/loader already set it in the response.\n return {\n shouldSetSiteCookie: cacheSite && !isSettingSiteCookieInResponse && existingSiteCookie !== site.id,\n shouldSetLocaleCookie: cacheLocale && !isSettingLocaleCookieInResponse && existingLocaleCookie !== locale.id,\n };\n}\n\n/**\n * Creates a multi-site middleware that resolves the current site from\n * the request (path, cookie, header, query, or default) and stores the\n * result in the router context.\n *\n * Does not import or read from app config context; the consumer supplies config.\n */\nexport function createMultiSiteMiddleware(config: MultiSiteConfig): MiddlewareFunction<Response> {\n // Merge config with defaults so every detection option has a value\n const siteDetectionConfig: MultiSiteSettings['siteDetectionConfig'] = {\n ...DEFAULT_SITE_DETECTION,\n ...config.siteDetectionConfig,\n };\n const localeDetectionConfig: MultiSiteSettings['localeDetectionConfig'] = {\n ...DEFAULT_LOCALE_DETECTION,\n ...config.localeDetectionConfig,\n };\n\n // Create cookies based on configured names\n const siteCookie = createMultiSiteCookie(siteDetectionConfig.lookupCookie);\n const localeCookie = createMultiSiteCookie(localeDetectionConfig.lookupCookie);\n\n const settings: MultiSiteSettings = {\n ...config,\n siteDetectionConfig,\n localeDetectionConfig,\n siteCookie,\n localeCookie,\n };\n\n const multiSiteMiddleware: MiddlewareFunction<Response> = async (\n { request, context }: MiddlewareArgs,\n next: () => Promise<Response>\n ): Promise<Response> => {\n const site = await resolveSite(request, settings);\n const locale = await resolveLocale(request, settings, site);\n\n // Store full Site, Locale, and Cookie objects in context for downstream middlewares (currency, loaders, etc.)\n context.set(multiSiteContext, {\n site,\n locale,\n siteCookie: settings.siteCookie,\n localeCookie: settings.localeCookie,\n });\n\n // Store locale in a WeakMap so i18next's findLocale can access it\n // This is necessary because findLocale only receives Request and cannot access the router context\n requestToLocaleMap.set(request, locale.id);\n\n const response = await next();\n\n // Determine if cookies should be set\n const { shouldSetSiteCookie, shouldSetLocaleCookie } = await shouldSetCookies(\n request,\n response,\n settings,\n site,\n locale\n );\n\n // Early return if no cookies need to be set\n if (!shouldSetSiteCookie && !shouldSetLocaleCookie) {\n return response;\n }\n\n const [siteSetCookie, localeSetCookie] = await Promise.all([\n shouldSetSiteCookie ? settings.siteCookie.serialize(site.id, { path: '/' }) : Promise.resolve(null),\n shouldSetLocaleCookie ? settings.localeCookie.serialize(locale.id, { path: '/' }) : Promise.resolve(null),\n ]);\n\n if (siteSetCookie) response.headers.append('Set-Cookie', siteSetCookie);\n if (localeSetCookie) response.headers.append('Set-Cookie', localeSetCookie);\n\n return response;\n };\n\n return multiSiteMiddleware;\n}\n"],"mappings":"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"}
|